If decoding is successful, image.Decode() (as well as special decoding functions such as jpeg.Decode() ) return image.Image . image.Image is an interface that defines read-only viewing of an image: it does not provide methods for changing / drawing an image.
The image package provides several image.Image implementations that allow you to modify / draw an image, usually using the Set(x, y int, c color.Color) .
image.Decode() however does not guarantee that the returned image will be any of the image types defined in the image package, or even that the dynamic image type has the Set() method (it can, but without warranty). Registered custom image decoders can return you the value of image.Image , which is a custom implementation (this does not mean the type of image defined in the image package).
If the (dynamic type) of the image has the Set() method, you can use the statement type and use its Set() method to draw on it. Here's how to do it:
type Changeable interface { Set(x, y int, c color.Color) } imgfile, err := os.Open("unchanged.jpg") if err != nil { panic(err.Error()) } defer imgfile.Close() img, err := jpeg.Decode(imgfile) if err != nil { panic(err.Error()) } if cimg, ok := img.(Changeable); ok { // cimg is of type Changeable, you can call its Set() method (draw on it) cimg.Set(0, 0, color.RGBA{85, 165, 34, 255}) cimg.Set(0, 1, color.RGBA{255, 0, 0, 255}) // when done, save img as usual } else { // No luck... see your options below }
If the image does not have the Set() method, you can choose to “override its representation” by executing a custom type that implements image.Image , but in its At(x, y int) color.Color (which returns / supplies color pixels) you are returning new colors that you would set if the image were mutable and return pixels in the original image where you would not change the image.
The implementation of the image.Image interface image.Image easiest to do using embedding, so you only need to make the necessary changes. Here's how to do it:
type MyImg struct { // Embed image.Image so MyImg will implement image.Image // because fields and methods of Image will be promoted: image.Image } func (m *MyImg) At(x, y int) color.Color { // "Changed" part: custom colors for specific coordinates: switch { case x == 0 && y == 0: return color.RGBA{85, 165, 34, 255} case x == 0 && y == 1: return color.RGBA{255, 0, 0, 255} } // "Unchanged" part: the colors of the original image: return m.Image.At(x, y) }
Usage: extremely simple. Download the image just like you, but when saving, specify the value of our type MyImg , which will take care of providing the changed image content (colors) when it asks for the encoder:
jpeg.Encode(outFile, &MyImg{img}, nil)
If you need to change a lot of pixels, it is impractical to include everything in the At() method. To do this, we can expand our MyImg to have an implementation of Set() , which stores the pixels that we want to change. Implementation Example:
type MyImg struct { image.Image custom map[image.Point]color.Color } func NewMyImg(img image.Image) *MyImg { return &MyImg{img, map[image.Point]color.Color{}} } func (m *MyImg) Set(x, y int, c color.Color) { m.custom[image.Point{x, y}] = c } func (m *MyImg) At(x, y int) color.Color {
Using it:
// Load image as usual, then my := NewMyImg(img) my.Set(0, 0, color.RGBA{85, 165, 34, 1}) my.Set(0, 1, color.RGBA{255, 0, 0, 255}) // And when saving, save 'my' instead of the original: jpeg.Encode(outFile, my, nil)
If you need to change a lot of pixels, then it would be more profitable to just create a new image that supports changing its pixels, for example. image.RGBA , draw the original image on it, and then go on to change the pixels you want.
To draw an image on another, you can use the image/draw package.
cimg := image.NewRGBA(img.Bounds()) draw.Draw(cimg, img.Bounds(), img, image.Point{}, draw.Over) // Now you have cimg which contains the original image and is changeable // (it has a Set() method) cimg.Set(0, 0, color.RGBA{85, 165, 34, 255}) cimg.Set(0, 1, color.RGBA{255, 0, 0, 255}) // And when saving, save 'cimg' of course: jpeg.Encode(outFile, cimg, nil)
The above code is for demonstration purposes only. In "real" images, Image.Bounds() may return a rectangle that does not start at the point (0;0) , in which case some adjustment will be required to make it work.