├── .gitignore ├── LICENSE ├── README.md ├── colors.go ├── images ├── amarao.jpg ├── amarao_grayscale.png ├── catac.jpg ├── catac_grayscale.png ├── integrating_pistol.jpg ├── integrating_pistol_grayscale.png ├── kirby.png ├── kirby_grayscale.png ├── pathetic.jpg ├── pathetic_grayscale.png ├── skate_or_die.jpg ├── skate_or_die_collage.jpg ├── skate_or_die_greenscale.png ├── wizard_cat.png └── wizard_cat_grayscale.png ├── main.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.exe 3 | gbprinter 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gbprinter 2 | Convert images to GameBoy screenshots. 3 | 4 | Installation 5 | --- 6 | 7 | ```bash 8 | go get github.com/ProfOak/gbprinter 9 | ``` 10 | 11 | Usage 12 | --- 13 | 14 | ```bash 15 | gbprinter image.png 16 | ``` 17 | 18 | The resulting image will be named `image_color-palette.png` 19 | 20 | 21 | Extra 22 | --- 23 | 24 | ```bash 25 | gbprinter -help 26 | Usage of gbprinter: 27 | -palette string 28 | Color choices: down, downa, downb, grayscale, greenscale, left, lefta, leftb, right, righta, rightb, up, upa, upb (default "grayscale") 29 | ``` 30 | 31 | Examples 32 | --- 33 | 34 | ### Grayscales 35 | 36 | ![original](images/amarao.jpg) 37 | ![grayscale](images/amarao_grayscale.png) 38 | 39 | ![original](images/catac.jpg) 40 | ![grayscale](images/catac_grayscale.png) 41 | 42 | ![original](images/integrating_pistol.jpg) 43 | ![grayscale](images/integrating_pistol_grayscale.png) 44 | 45 | ![original](images/kirby.png) 46 | ![grayscale](images/kirby_grayscale.png) 47 | 48 | ![original](images/wizard_cat.png) 49 | ![grayscale](images/wizard_cat_grayscale.png) 50 | 51 | ### Greenscale and the other color palettes 52 | 53 | ![original](images/skate_or_die.jpg) 54 | ![greenscale](images/skate_or_die_greenscale.png) 55 | ![all of the color palettes](images/skate_or_die_collage.jpg) 56 | -------------------------------------------------------------------------------- /colors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "sort" 6 | ) 7 | 8 | // Palette contains four colors because the GameBoy only had four colors. 9 | // This was extended later on to allow each sprite to have their own set of 10 | // four colors, in the GameBoy Color. 11 | type Palette struct { 12 | Black color.RGBA 13 | DarkGray color.RGBA 14 | LightGray color.RGBA 15 | White color.RGBA 16 | } 17 | 18 | // ColorPalettes are a collection of the classic GB and GBC palettes. 19 | type ColorPalettes = map[string]*Palette 20 | 21 | var palettes = ColorPalettes{ 22 | "grayscale": &Palette{ 23 | Black: color.RGBA{0, 0, 0, 255}, 24 | DarkGray: color.RGBA{63, 63, 63, 255}, 25 | LightGray: color.RGBA{157, 157, 157, 255}, 26 | White: color.RGBA{255, 255, 255, 255}, 27 | }, "greenscale": &Palette{ 28 | Black: color.RGBA{15, 56, 15, 255}, 29 | DarkGray: color.RGBA{48, 98, 48, 255}, 30 | LightGray: color.RGBA{139, 172, 15, 255}, 31 | White: color.RGBA{155, 188, 15, 255}, 32 | }, "up": &Palette{ 33 | Black: color.RGBA{0, 0, 0, 255}, 34 | DarkGray: color.RGBA{131, 49, 0, 255}, 35 | LightGray: color.RGBA{255, 173, 99, 255}, 36 | White: color.RGBA{255, 255, 255, 255}, 37 | }, "upa": &Palette{ 38 | Black: color.RGBA{0, 0, 0, 255}, 39 | DarkGray: color.RGBA{148, 58, 58, 255}, 40 | LightGray: color.RGBA{255, 133, 132, 255}, 41 | White: color.RGBA{255, 255, 255, 255}, 42 | }, "upb": &Palette{ 43 | Black: color.RGBA{91, 49, 9, 255}, 44 | DarkGray: color.RGBA{132, 107, 41, 255}, 45 | LightGray: color.RGBA{206, 156, 133, 255}, 46 | White: color.RGBA{255, 231, 197, 255}, 47 | }, "left": &Palette{ 48 | Black: color.RGBA{0, 0, 0, 255}, 49 | DarkGray: color.RGBA{0, 0, 254, 255}, 50 | LightGray: color.RGBA{101, 164, 155, 255}, 51 | White: color.RGBA{255, 255, 255, 255}, 52 | }, "lefta": &Palette{ 53 | Black: color.RGBA{0, 0, 0, 255}, 54 | DarkGray: color.RGBA{83, 82, 140, 255}, 55 | LightGray: color.RGBA{139, 140, 222, 255}, 56 | White: color.RGBA{255, 255, 255, 255}, 57 | }, "leftb": &Palette{ 58 | Black: color.RGBA{0, 0, 0, 255}, 59 | DarkGray: color.RGBA{82, 82, 82, 255}, 60 | LightGray: color.RGBA{165, 165, 165, 255}, 61 | White: color.RGBA{255, 255, 255, 255}, 62 | }, "down": &Palette{ 63 | Black: color.RGBA{0, 0, 0, 255}, 64 | DarkGray: color.RGBA{147, 148, 254, 255}, 65 | LightGray: color.RGBA{254, 148, 148, 255}, 66 | White: color.RGBA{255, 255, 165, 255}, 67 | }, "downa": &Palette{ 68 | Black: color.RGBA{0, 0, 0, 255}, 69 | DarkGray: color.RGBA{254, 0, 0, 255}, 70 | LightGray: color.RGBA{255, 255, 0, 255}, 71 | White: color.RGBA{255, 255, 255, 255}, 72 | }, "downb": &Palette{ 73 | Black: color.RGBA{0, 0, 0, 255}, 74 | DarkGray: color.RGBA{125, 73, 0, 255}, 75 | LightGray: color.RGBA{255, 255, 0, 255}, 76 | White: color.RGBA{255, 255, 255, 255}, 77 | }, "right": &Palette{ 78 | Black: color.RGBA{0, 0, 0, 255}, 79 | DarkGray: color.RGBA{255, 66, 0, 255}, 80 | LightGray: color.RGBA{81, 255, 0, 255}, 81 | White: color.RGBA{255, 255, 255, 255}, 82 | }, "righta": &Palette{ 83 | Black: color.RGBA{0, 0, 0, 255}, 84 | DarkGray: color.RGBA{1, 99, 198, 255}, 85 | LightGray: color.RGBA{123, 255, 48, 255}, 86 | White: color.RGBA{255, 255, 255, 255}, 87 | }, "rightb": &Palette{ 88 | Black: color.RGBA{255, 255, 255, 255}, 89 | DarkGray: color.RGBA{255, 255, 0, 255}, 90 | LightGray: color.RGBA{0, 132, 134, 255}, 91 | White: color.RGBA{0, 0, 0, 255}, 92 | }, 93 | } 94 | 95 | // paletteNames is an easy way to generate the key names of the palettes map. 96 | func paletteNames() []string { 97 | 98 | var names []string 99 | for name := range palettes { 100 | names = append(names, name) 101 | } 102 | sort.Strings(names) 103 | return names 104 | } 105 | -------------------------------------------------------------------------------- /images/amarao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/amarao.jpg -------------------------------------------------------------------------------- /images/amarao_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/amarao_grayscale.png -------------------------------------------------------------------------------- /images/catac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/catac.jpg -------------------------------------------------------------------------------- /images/catac_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/catac_grayscale.png -------------------------------------------------------------------------------- /images/integrating_pistol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/integrating_pistol.jpg -------------------------------------------------------------------------------- /images/integrating_pistol_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/integrating_pistol_grayscale.png -------------------------------------------------------------------------------- /images/kirby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/kirby.png -------------------------------------------------------------------------------- /images/kirby_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/kirby_grayscale.png -------------------------------------------------------------------------------- /images/pathetic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/pathetic.jpg -------------------------------------------------------------------------------- /images/pathetic_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/pathetic_grayscale.png -------------------------------------------------------------------------------- /images/skate_or_die.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/skate_or_die.jpg -------------------------------------------------------------------------------- /images/skate_or_die_collage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/skate_or_die_collage.jpg -------------------------------------------------------------------------------- /images/skate_or_die_greenscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/skate_or_die_greenscale.png -------------------------------------------------------------------------------- /images/wizard_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/wizard_cat.png -------------------------------------------------------------------------------- /images/wizard_cat_grayscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProfOak/gbprinter/29825d9b5880b185c91aa0b4167541c8f91cb393/images/wizard_cat_grayscale.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/nfnt/resize" 7 | "image" 8 | "image/color" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | var ( 14 | // Split 0-255 into 4 parts to come up with the GameBoy colors. 15 | black uint8 16 | darkGray uint8 = 84 17 | lightGray uint8 = 169 18 | white uint8 = 255 19 | 20 | // I couldn't decide whether or not to use color values or in-between color 21 | // values for determining what new color to translate the original pixel's 22 | // color value into. Some images looked better using the hard boundries 23 | // of the four color values above. Some images looked better using the three 24 | // in between values below. Hold onto the in-between values in case I revisit 25 | // this in the future. 26 | 27 | // Find the average between the neighboring colors and use them as 28 | // color boundries that the image will round down to. 29 | low = (black + darkGray) / 2 30 | medium = (darkGray + lightGray) / 2 31 | high = (lightGray + white) / 2 32 | 33 | // This is the original GameBoy's resolution. 34 | height = 144 35 | width = 160 36 | ) 37 | 38 | func main() { 39 | 40 | var palette *Palette 41 | 42 | choiceMessage := "Color choices: " + strings.Join(paletteNames(), ", ") 43 | paletteChoice := flag.String("palette", "grayscale", choiceMessage) 44 | flag.Parse() 45 | 46 | if p, ok := palettes[*paletteChoice]; ok { 47 | palette = p 48 | } else { 49 | fmt.Printf("Invalid color palette: %s\n", *paletteChoice) 50 | fmt.Println(choiceMessage) 51 | os.Exit(1) 52 | } 53 | 54 | for _, filename := range flag.Args() { 55 | 56 | // Open the image. 57 | sourceImage := openImage(filename) 58 | 59 | // TODO: Figure out how to get better colors. 60 | // Calculate the average brightness using the relative luminance formula. 61 | //brightness := averageBrightness(sourceImage) 62 | 63 | // Configure the global variables in main to reflect the average brightness. 64 | //configureColors(brightness) 65 | 66 | // Resize and recolor the image based on the color palette. 67 | output := gbPrinter(sourceImage, palette) 68 | 69 | // format: sourceName_paletteName.png 70 | filename = fmt.Sprintf( 71 | "%s_%s.png", strings.Split(filename, ".")[0], *paletteChoice) 72 | saveImage(filename, output) 73 | } 74 | } 75 | 76 | // gbPrinter takes a source image and produces a new image based on the palette. 77 | func gbPrinter(sourceImage image.Image, palette *Palette) image.Image { 78 | 79 | sourceImage = resize.Resize( 80 | uint(width), 81 | uint(height), 82 | sourceImage, 83 | resize.NearestNeighbor, 84 | ) 85 | 86 | screen := image.NewRGBA(image.Rect(0, 0, width, height)) 87 | 88 | for x := 0; x < sourceImage.Bounds().Max.X; x++ { 89 | for y := 0; y < sourceImage.Bounds().Max.Y; y++ { 90 | screen.Set(x, y, transformColor(sourceImage.At(x, y), palette)) 91 | } 92 | } 93 | 94 | return screen 95 | } 96 | 97 | // configureColors will change the global variables based on the brightness 98 | // of the current image. 99 | func configureColors(brightness uint8) { 100 | 101 | var ceiling float32 102 | 103 | // Use this as an upper limit of how high the white color can go. 104 | // Also try to prevent overflows. 105 | brightness = min(brightness, 127) 106 | brightness *= 2 107 | 108 | ceiling = float32(brightness) 109 | 110 | white = 255 111 | lightGray = uint8(ceiling) 112 | darkGray = uint8(ceiling * (2.0 / 3.0)) 113 | black = 0 114 | 115 | low = (black + darkGray) / 2 116 | medium = (darkGray + lightGray) / 2 117 | high = (lightGray + white) / 2 118 | } 119 | 120 | // averageBrightness is used to configure the w/b color values. 121 | func averageBrightness(img image.Image) uint8 { 122 | 123 | var sum int 124 | for x := 0; x < img.Bounds().Max.X; x++ { 125 | for y := 0; y < img.Bounds().Max.Y; y++ { 126 | sum += int(relativeLuminance(img.At(x, y))) 127 | } 128 | } 129 | 130 | return uint8(sum / (height * width)) 131 | } 132 | 133 | // relativeLuminance calculates the relative luminance from a pixel of an image 134 | // and effectively returns the brightness of a pixel. 135 | // source: https://en.wikipedia.org/wiki/Relative_luminance 136 | func relativeLuminance(currentColor color.Color) uint8 { 137 | 138 | // Color.RGBA returns uint32 but color.RBGA uses uint8 139 | tempr, tempg, tempb, _ := currentColor.RGBA() 140 | 141 | // RGBA() on a uint8 color is bit shifted left by 8. Undo this. 142 | r := uint8(tempr >> 8) 143 | g := uint8(tempg >> 8) 144 | b := uint8(tempb >> 8) 145 | 146 | // This number is an approximation. 147 | return uint8((0.2126 * float32(r)) + 148 | (0.715 * float32(g)) + 149 | (0.0722 * float32(b))) 150 | } 151 | 152 | // transformColor takes a pixel from an image and calculates the brightness. 153 | // The brightness value is then rounded down to one of the 4 gameboy colors. 154 | // The given palette is used to replace the color with another color scheme 155 | // from the GameBoy color. 156 | func transformColor(oldColor color.Color, palette *Palette) color.RGBA { 157 | 158 | var newColor color.RGBA 159 | 160 | // This number is an approximation. 161 | relative := relativeLuminance(oldColor) 162 | 163 | // Round the relative value to the nearest GameBoy color pallet value. 164 | if relative < darkGray { 165 | newColor = palette.Black 166 | } else if relative < lightGray { 167 | newColor = palette.DarkGray 168 | } else if relative < white { 169 | newColor = palette.LightGray 170 | } else { 171 | newColor = palette.White 172 | } 173 | 174 | return newColor 175 | } 176 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | _ "image/gif" 6 | _ "image/jpeg" 7 | "image/png" 8 | "log" 9 | "os" 10 | ) 11 | 12 | // saveImage reduces the boilerplate code needed to save an image. 13 | func saveImage(filename string, img image.Image) { 14 | 15 | f, err := os.Create(filename) 16 | check(err) 17 | if err != nil { 18 | log.Fatalf("Error creating file: %s\n", filename) 19 | } 20 | defer f.Close() 21 | 22 | err = png.Encode(f, img) 23 | check(err) 24 | } 25 | 26 | // openImage reduces the boilerplate code needed to open an image. 27 | func openImage(filename string) image.Image { 28 | 29 | inputFile, err := os.Open(filename) 30 | check(err) 31 | 32 | img, _, err := image.Decode(inputFile) 33 | check(err) 34 | return img 35 | } 36 | 37 | // check reduces the boilerplate code for checking error values. 38 | func check(err error) { 39 | 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | // min is a helper function to calculate the min of two uint8s. The min function 46 | // in the standard library only operates on float64. 47 | func min(a, b uint8) uint8 { 48 | 49 | if a < b { 50 | return a 51 | } 52 | return b 53 | } 54 | --------------------------------------------------------------------------------