19

I want to open jpeg image file, encode it, change some pixel colors, and then save it back as it was.

I'd like to do something like this

imgfile, err := os.Open("unchanged.jpeg")
defer imgfile.Close()
if err != nil {
    fmt.Println(err.Error())
}

img,err := jpeg.Decode(imgfile)
if err != nil {
    fmt.Println(err.Error())
}
img.Set(0, 0, color.RGBA{85, 165, 34, 1})
img.Set(1,0,....)

outFile, _ := os.Create("changed.jpeg")
defer outFile.Close()
jpeg.Encode(outFile, img, nil)

I just can't come up with a working solution, since default image type that I get after encoding image-file doesn't have Set method.

Can anyone explain how to do this? Thanks a lot.

wasmup
  • 14,541
  • 6
  • 42
  • 58
Matúš Čongrády
  • 1,340
  • 3
  • 17
  • 29

3 Answers3

23

On successful decoding image.Decode() (and also specific decoding functions like jpeg.Decode()) return a value of image.Image. image.Image is an interface which defines a read-only view of an image: it does not provide methods to change / draw on the image.

The image package provides several image.Image implementations which allow you to change / draw on the image, usually with a Set(x, y int, c color.Color) method.

image.Decode() however does not give you any guarantee that the returned image will be any of the image types defined in the image package, or even that the dynamic type of the image has a Set() method (it may, but no guarantee). Registered custom image decoders may return you an image.Image value being a custom implementation (meaning not an image type defined in the image package).

If the (dynamic type of the) image does have a Set() method, you may use type assertion and use its Set() method to draw on it. This is how it can be done:

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 a Set() method, you may choose to "override its view" by implementing a custom type which implements image.Image, but in its At(x, y int) color.Color method (which returns / supplies the colors of pixels) you return the new colors that you would set if the image would be changeable, and return the pixels of the original image where you would not change the image.

Implementing the image.Image interface is easiest done by utilizing embedding, so you only need to implement the changes you want. This is how it can be done:

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)
}

Using it: extremely simple. Load the image as you did, but when saving, provide a value of our MyImg type which will take care of providing the altered image content (colors) when it is asked by the encoder:

jpeg.Encode(outFile, &MyImg{img}, nil)

If you have to change many pixels, it's not practical to include all in the At() method. For that we can extend our MyImg to have our Set() implementation which stores the pixels that we want to change. Example implementation:

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 {
    // Explicitly changed part: custom colors of the changed pixels:
    if c := m.custom[image.Point{x, y}]; c != nil {
        return c
    }
    // Unchanged part: colors of the original image:
    return m.Image.At(x, y)
}

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 have to change many pixels, then it might be more profitable to just create a new image which supports changing its pixels, e.g. image.RGBA, draw the original image on it and then proceed to change pixels you want to.

To draw an image onto 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 just for demonstration. In "real-life" images Image.Bounds() may return a rectangle that does not start at (0;0) point, in which case some adjustment would be needed to make it work.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Even looking through the go source for those packages, I don't know how you were able to determine that you can create an interface with Set on it. How did you arrive at that conclusion? – Justin Self May 27 '17 at 19:49
  • 1
    @JustinSelf The different concrete `image.Image` implementations in the `image` package all have a `Set(x, y int, c color.Color)` method. So image decoding functions of the standard library are highly likely to return an image implementation that will have this `Set()` method. So that's why we should check that first. If the image does not have a `Set()` method, then we can name our custom `Set()` method whatever we like (it doesn't matter as long as only we will call it), I just used the same method signature for clarity. – icza May 28 '17 at 10:11
  • @icza There's something I don't understand. The op says that he is reading the image from a file but your solutions look like they're using an image struct that has been initialised instead of being decoded from an image in a file. For example, the code `jpeg.Encode(outFile, &MyImg{img}, nil)` has `MyImg{img}` which doesn't seem to have been decoded from a file. Where is your equivalent of the op's `img,err := jpeg.Decode(imgfile)`? – bit Sep 10 '22 at 12:06
  • @icza I seemed to have missed this `Using it: extremely simple. Load the image as you did, but when saving, provide a value of our MyImg type which will take care of providing the altered image content (colors) when it is asked by the encoder:`. Additionally, `draw.Draw(cimg, img.Bounds(), img, image.Point{}, draw.Over)` seems to use the image from the file, but this is only for your last approach. – bit Sep 10 '22 at 12:50
  • or maybe `img` in `my := NewMyImg(img)` is the image object created in `img,err := jpeg.Decode(imgfile)`? – bit Sep 10 '22 at 12:53
  • @bit Yes, the `img` passed to `NewMyImg(img)` and the one used in the composite literal `&MyImg{img}` is the one loaded and decoded from the file (the original image). – icza Sep 10 '22 at 12:56
2

The image decode returns an image interface which has a Bounds method to obtain the image pixel width and height.

img, _, err := image.Decode(imgfile)
if err != nil {
    fmt.Println(err.Error())
}
size := img.Bounds().Size()

Once you have the width and height you can iterate over the pixels with two nested for loops (one for the x and one for y coordinate).

for x := 0; x < size.X; x++ {
    for y := 0; y < size.Y; y++ {
        color := color.RGBA{
            uint8(255 * x / size.X),
            uint8(255 * y / size.Y),
            55,
            255}
        m.Set(x, y, color)
    }
}

Once your are done with the image manipulation you can encode the file. But because image.Image does not have Set method, you can create a new RGBA image, which returns an RGBA struct, on which you can use the Set method.

m := image.NewRGBA(image.Rect(0, 0, width, height))
outFile, err := os.Create("changed.jpg")
if err != nil {
    log.Fatal(err)
}
defer outFile.Close()
png.Encode(outFile, m)
Endre Simo
  • 11,330
  • 2
  • 40
  • 49
  • I can't use this. I still cant use Set method, since my encoded image is of type image.Image, which doesnt have Set method. Looping over whole image isn't what I'd like to do either, I need to change just a few pixels. Thanks for the answer anyway – Matúš Čongrády Apr 12 '16 at 13:07
  • How about to create a new RGBA image, which returns RGBA interface, on which you can use the `Set` method `m := image.NewRGBA(image.Rect(0, 0, width, height))` – Endre Simo Apr 12 '16 at 13:14
  • Aight, I just thought I can draw pixels directly to my encoded image. Creating new image and copying content of old encoded image just so I can change a few pixels seems a bit odd to me. – Matúš Čongrády Apr 12 '16 at 13:22
0

image.Image is immutable by default, but draw.Image is mutable.

If you do a type conversion to draw.Image, that should give you a Set method

img.(draw.Image).Set(0,0, color.RGBA{85, 165, 34, 1})
matt.s
  • 1,698
  • 1
  • 20
  • 29