Slide 1

Slide 1 text

Drawing gophers with Go A more fun Go introduction for beginners Quasilyte @ GolangKazan, 2019

Slide 2

Slide 2 text

Questions that are asked ● Who uses Go?

Slide 3

Slide 3 text

Questions that are asked ● Who uses Go? ● Are there really big examples of Go software?

Slide 4

Slide 4 text

Questions that are asked ● Who uses Go? ● Are there really big examples of Go software? ● What tasks are usually solved with Go?

Slide 5

Slide 5 text

Questions that are asked ● Who uses Go? ● Are there really big examples of Go software? ● What tasks are usually solved with Go? ● What are the main advantages of using Go?

Slide 6

Slide 6 text

Questions that are asked ● Who uses Go? ● Are there really big examples of Go software? ● What tasks are usually solved with Go? ● What are the main advantages of using Go? ● Why Go instead of XYZ language?

Slide 7

Slide 7 text

Questions that are asked ● Who uses Go? ● Are there really big examples of Go software? ● What tasks are usually solved with Go? ● What are the main advantages of using Go? ● Why Go instead of XYZ language?

Slide 8

Slide 8 text

Infrastructure ● Dev tools ● Automation tools ● Build tools, generators ● Monitoring systems ● Specialized databases ● Integration layers ● System utilities

Slide 9

Slide 9 text

Backend ● Web (app) servers ● Proxies ● Background workers ● ETL programs ● Microservices ● APIs

Slide 10

Slide 10 text

Other ● Chat bots ● AD tech ● Blockchain ● Embedded* ● ML infrastructure*

Slide 11

Slide 11 text

Of course you can use Go for everything, but some areas lack good libraries.

Slide 12

Slide 12 text

We’ll do some simple image processing using Go standard library.

Slide 13

Slide 13 text

Standard library ● image - basic 2D image library ● image/draw - image composition functions ● image/color - basic color library ● image/color/palette - standard color palettes ● image/{png,jpeg,gif} - encoders/decoders

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Workflow 1. Get an “image object” ○ Read (decode) an image file ○ Create programmatically

Slide 16

Slide 16 text

Workflow 1. Get an “image object” ○ Read (decode) an image file ○ Create programmatically 2. Apply transformations to the object

Slide 17

Slide 17 text

Workflow 1. Get an “image object” ○ Read (decode) an image file ○ Create programmatically 2. Apply transformations to the object 3. Write (encode) an object to a file

Slide 18

Slide 18 text

We’ll start with something simple!

Slide 19

Slide 19 text

example.go: snippet 1 (imports) package main import ( "image" // 2D types and funcs "image/color" // To work with colors "image/png" // We’ll save it as PNG "os" // To create a new file ) func main() { /* see next slide */ }

Slide 20

Slide 20 text

example.go: snippet 1 (imports) package main import ( "image" // 2D types and funcs "image/color" // To work with colors "image/png" // We’ll save it as PNG "os" // To create a new file ) func main() { /* see next slide */ }

Slide 21

Slide 21 text

example.go: snippet 1 (imports) package main import ( "image" // 2D types and funcs "image/color" // To work with colors "image/png" // We’ll save it as PNG "os" // To create a new file ) func main() { /* see next slide */ }

Slide 22

Slide 22 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 23

Slide 23 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 24

Slide 24 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 25

Slide 25 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 26

Slide 26 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 27

Slide 27 text

example.go: snippet 2 (main without error handling) package main import ( /* see previous slide */ ) func main() { img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, _ := os.Create("art.png") png.Encode(f, img) }

Slide 28

Slide 28 text

Let’s run it! $ go run example.go

Slide 29

Slide 29 text

It’s so majestic! Not quite a gopher yet, though. Just a 3x3 PNG image with white pixel in the middle. art.png

Slide 30

Slide 30 text

Before we continue, we need to have a serious talk...

Slide 31

Slide 31 text

example.go: snippet 3 (main with error handling) img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, err := os.Create("art.png") if err != nil { // handle file creation error. } err = png.Encode(f, img) if err != nil { // handle image encoding error. }

Slide 32

Slide 32 text

example.go: snippet 3 (main with error handling) img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, err := os.Create("art.png") if err != nil { // handle file creation error. } err = png.Encode(f, img) if err != nil { // handle image encoding error. }

Slide 33

Slide 33 text

example.go: snippet 3 (main with error handling) img := image.NewGray(image.Rect(0, 0, 3, 3)) img.Set(1, 1, color.Gray{Y: 255}) f, err := os.Create("art.png") if err != nil { // handle file creation error. } err = png.Encode(f, img) if err != nil { // handle image encoding error. }

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

How? ? ? ? ?

Slide 36

Slide 36 text

We’ll draw a gopher by composing several images together.

Slide 37

Slide 37 text

Draw them over each other Sprites

Slide 38

Slide 38 text

Where can we get sprites? Assets can be borrowed from the GopherKon sprites set.

Slide 39

Slide 39 text

A program that renders sprites together will be called “compose”.

Slide 40

Slide 40 text

“Compose” program algorithm 1. Convert filename arguments to image objects. 2. Draw input images over output image. 3. Write output image object to a file. Our “compose” program will accept filename arguments and write them to a new file, one over another.

Slide 41

Slide 41 text

compose.go: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 42

Slide 42 text

compose.go: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 43

Slide 43 text

compose.go: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 44

Slide 44 text

compose.go: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 45

Slide 45 text

Compose: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 46

Slide 46 text

compose.go: snippet 1 (convert filenames) var layers []image.Image for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() img, _ := png.Decode(f) layers = append(layers, img) }

Slide 47

Slide 47 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 48

Slide 48 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 49

Slide 49 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 50

Slide 50 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 51

Slide 51 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 52

Slide 52 text

compose.go: snippet 2 (fill output image) bounds := image.Rect(0, 0, *width, *height) outImage := image.NewRGBA(bounds) draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) for _, layer := range layers[1:] { draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) }

Slide 53

Slide 53 text

compose.go: snippet 3 (write to a file) outFile, err := os.Create(outFilename) if err != nil { log.Fatalf("create file: %v", err) } err = png.Encode(outFile, outImage) if err != nil { log.Fatalf("encode: %v", err) }

Slide 54

Slide 54 text

Let’s run it! $ go run compose.go \ ears.png body.png eyes.png \ teeth.png undernose.png \ nose.png hands.png

Slide 55

Slide 55 text

Write ears.png

Slide 56

Slide 56 text

Write body.png

Slide 57

Slide 57 text

Write eyes.png

Slide 58

Slide 58 text

Write teeth.png

Slide 59

Slide 59 text

Write undernose.png

Slide 60

Slide 60 text

Write nose.png

Slide 61

Slide 61 text

Write hands.png

Slide 62

Slide 62 text

gopher.png is ready!

Slide 63

Slide 63 text

Any part can be changed to get an unique gopher, but the order of drawing is very important.

Slide 64

Slide 64 text

Complete example with assets: https://bit.ly/2lQuSHK

Slide 65

Slide 65 text

Can we convert images from one format to another?

Slide 66

Slide 66 text

Can we convert images from one format to another? We can!

Slide 67

Slide 67 text

Converting images 1. Decode an image in X format (png/jpeg/etc). 2. Encode that image in Y format (png/jpeg/etc). We’ll create png2jpg program that converts PNG images to JPEG images with specified quality.

Slide 68

Slide 68 text

png2jpg.go: encoding into JPEG img, err := png.Decode() if err != nil { log.Panicf("can’t decode input PNG") } opts := &jpeg.Options{Quality: 80} jpeg.Encode(outFile, img, opts)

Slide 69

Slide 69 text

png2jpg.go: encoding into JPEG img, err := png.Decode() if err != nil { log.Panicf("can’t decode input PNG") } opts := &jpeg.Options{Quality: 80} jpeg.Encode(outFile, img, opts)

Slide 70

Slide 70 text

Let’s run it! $ go run png2jpg.go gopher.png

Slide 71

Slide 71 text

OK, we get a JPEG gopher, but it looks a lot like original PNG… What if we change encoding quality option?

Slide 72

Slide 72 text

png2jpg.go: command-line flags quality := flag.Int( "q", 80, "output JPEG quality") flag.Parse() // ^ several lines above opts := &jpeg.Options{Quality: *quality} jpeg.Encode(outFile, img, opts)

Slide 73

Slide 73 text

png2jpg.go: command-line flags quality := flag.Int( "q", 80, "output JPEG quality") flag.Parse() // ^ several lines above opts := &jpeg.Options{Quality: *quality} jpeg.Encode(outFile, img, opts)

Slide 74

Slide 74 text

png2jpg.go: command-line flags quality := flag.Int( "q", 80, "output JPEG quality") flag.Parse() // ^ several lines above opts := &jpeg.Options{Quality: *quality} jpeg.Encode(outFile, img, opts)

Slide 75

Slide 75 text

png2jpg.go: command-line flags quality := flag.Int( "q", 80, "output JPEG quality") flag.Parse() // ^ several lines above opts := &jpeg.Options{Quality: *quality} jpeg.Encode(outFile, img, opts)

Slide 76

Slide 76 text

png2jpg.go: command-line flags quality := flag.Int( "q", 80, "output JPEG quality") flag.Parse() // ^ several lines above opts := &jpeg.Options{Quality: *quality} jpeg.Encode(outFile, img, opts)

Slide 77

Slide 77 text

Let’s run it! $ go run png2jpg.go -q 1 gopher.png

Slide 78

Slide 78 text

png2jpg.go: quality impact q=100 q=30 q=1

Slide 79

Slide 79 text

png2jpg.go: quality impact q=100 q=30 q=1

Slide 80

Slide 80 text

png2jpg.go: quality impact q=100 q=30 q=1

Slide 81

Slide 81 text

What if you need a smaller gopher? There is no “resize” in stdlib. :(

Slide 82

Slide 82 text

Resize an image // import "github.com/nfnt/resize" algorithm := resize.Bicubic result:= resize.Resize(w, h, img, algorithm) // result contains resized image data.

Slide 83

Slide 83 text

Resize an image // import "github.com/nfnt/resize" algorithm := resize.Bicubic result:= resize.Resize(w, h, img, algorithm) // result contains resized image data.

Slide 84

Slide 84 text

Resize an image // import "github.com/nfnt/resize" algorithm := resize.Bicubic result:= resize.Resize(w, h, img, algorithm) // result contains resized image data.

Slide 85

Slide 85 text

Let’s run it! $ go run resize.go -w 100 gopher.png

Slide 86

Slide 86 text

github.com/nfnt/resize package resize

Slide 87

Slide 87 text

Time to try manipulating individual pixels in existing image. We’re about to invert gopher colors!

Slide 88

Slide 88 text

Inverting PNG colors 1. Decode PNG image, cast it to NRGBA. 2. Invert every individual pixel inside NRGBA. 3. Encode NRGBA to file. png.Decode returned type depends on the image contents. For our gopher it’s NRGBA.

Slide 89

Slide 89 text

invert.go: cast to NRGBA img, _ := png.Decode(f) dst, ok := img.(*image.NRGBA) if !ok { log.Panicf("not NRGBA") }

Slide 90

Slide 90 text

invert.go: cast to NRGBA img, _ := png.Decode(f) dst, ok := img.(*image.NRGBA) if !ok { log.Panicf("not NRGBA") }

Slide 91

Slide 91 text

invert.go: cast to NRGBA img, _ := png.Decode(f) dst, ok := img.(*image.NRGBA) if !ok { log.Panicf("not NRGBA") }

Slide 92

Slide 92 text

invert.go: loop over pixels bounds := dst.Bounds() height := bounds.Size().Y width := bounds.Size().X for y := 0; y < height; y++ { i := y * dst.Stride for x := 0; x < width; x++ { /* Loop body. See next slides */ } }

Slide 93

Slide 93 text

invert.go: loop over pixels bounds := dst.Bounds() height := bounds.Size().Y width := bounds.Size().X for y := 0; y < height; y++ { i := y * dst.Stride for x := 0; x < width; x++ { /* Loop body. See next slides */ } }

Slide 94

Slide 94 text

Invert.go: inverting colors d := dst.Pix[i : i+4 : i+4] // Invert colors. d[0] = 255 - d[0] // R d[1] = 255 - d[1] // G d[2] = 255 - d[2] // B d[3] = d[3] // Alpha, unchanged i += 4 // Go to the next RGBA component.

Slide 95

Slide 95 text

Invert.go: inverting colors d := dst.Pix[i : i+4 : i+4] // Invert colors. d[0] = 255 - d[0] // R d[1] = 255 - d[1] // G d[2] = 255 - d[2] // B d[3] = d[3] // Alpha, unchanged i += 4 // Go to the next RGBA component.

Slide 96

Slide 96 text

Invert.go: inverting colors d := dst.Pix[i : i+4 : i+4] // Invert colors. d[0] = 255 - d[0] // R d[1] = 255 - d[1] // G d[2] = 255 - d[2] // B d[3] = d[3] // Alpha, unchanged i += 4 // Go to the next RGBA component.

Slide 97

Slide 97 text

If you’re confused about pixels layout, here goes the explanation.

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

NRGBA structure

Slide 100

Slide 100 text

NRGBA structure

Slide 101

Slide 101 text

We store 2-D information inside 1-D array for efficiency. This is why we need a “stride” and “i” index calculation.

Slide 102

Slide 102 text

invert.go: loop over pixels for y := 0; y < height; y++ { i := y * dst.Stride for x := 0; x < width; x++ { d := dst.Pix[i : i+4 : i+4] /* ...rest of the loop. */ } }

Slide 103

Slide 103 text

Let’s run it! $ go run invert.go gopher.png

Slide 104

Slide 104 text

github.com/disintegration/imaging package imaging invert

Slide 105

Slide 105 text

github.com/disintegration/imaging package ● Crop, fit, resize ● Grayscale, invert, blur ● Convolutions ● Transpose, transverse, flip, rotate ● And more!

Slide 106

Slide 106 text

Programs we created today (links): compose png2jpg resize invert

Slide 107

Slide 107 text

Please, ask questions! ^ Slides ^ https://bit.ly/2ksX833