19

Go has great image manipulation and data libraries however I'm having trouble creating one big image from smaller ones. Does anyone know how to take two png or jpeg files in Golang and concatenate them to form one big image that encompasses the two (or more) files?

i'm currently reading png files like so:

imgFile, err := os.Open(path)
if err != nil {
    return Image{}, err
}
img, _, err := image.Decode(imgFile)
if err != nil {
    return Image{}, err
}

rgba := image.NewRGBA(img.Bounds())
if rgba.Stride != rgba.Rect.Size().X*4 {
    return Image{}, fmt.Errorf("unsupported stride")
}
draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)

I'm confused on how to take this png RGBA data and concatenate with other RGBA data and/or combine that into an "empty" image.

efel
  • 1,054
  • 3
  • 14
  • 29
  • 3
    What kind of trouble are you having? What have you tried so far? In any case, the general princinple is to create a new, big target image and then copy pixels from the source images into the target image. – LemurFromTheId Mar 12 '16 at 23:45
  • Well so far I am only able to take the pixel data from a png and form a new RGBA and then decode that internally as a png which I pass to OpenGL. However, i'm not quite sure how to take this "decoded" png data that I read from the .png file and copy/create a bigger image from that – efel Mar 12 '16 at 23:59
  • I created a package that handles concatenation on either x or y-axis and allows alignment options as well https://github.com/dillonstreator/imagecat – dillon May 16 '23 at 15:56

4 Answers4

40

Create a new empty image (NewRGBA) that has bounds large enough to hold both images. Then use the Draw method to draw each image on appropriate parts of this new large image.

Here are steps with code.

Load two images.

imgFile1, err := os.Open("test1.jpg")
if err != nil {
    fmt.Println(err)
}
imgFile2, err := os.Open("test2.jpg")
if err != nil {
    fmt.Println(err)
}
img1, _, err := image.Decode(imgFile1)
if err != nil {
    fmt.Println(err)
}
img2, _, err := image.Decode(imgFile2)
if err != nil {
    fmt.Println(err)
}

Let's draw the second image to the right of the first image. So the starting point of it should be at (w, 0) where w is the width of the first image. The bottom right point of the first image will be the bottom left point of the second.

//starting position of the second image (bottom left)
sp2 := image.Point{img1.Bounds().Dx(), 0}

It should be in a rectangle large enough to hold it.

//new rectangle for the second image
r2 := image.Rectangle{sp2, sp2.Add(img2.Bounds().Size())}

Now create a large rectangle that will be wide enough to hold both images.

//rectangle for the big image
r := image.Rectangle{image.Point{0, 0}, r2.Max}

Note This large image will have the height of the second image. If the first image is higher it will be cropped.

Create a new image.

rgba := image.NewRGBA(r)

Now you can draw the two images into this new image

draw.Draw(rgba, img1.Bounds(), img1, image.Point{0, 0}, draw.Src)
draw.Draw(rgba, r2, img2, image.Point{0, 0}, draw.Src)

Since we created r2 so its to the right of the first image, second image will be drawn to the right.

Finally you can export it.

out, err := os.Create("./output.jpg")
if err != nil {
    fmt.Println(err)
}

var opt jpeg.Options
opt.Quality = 80

jpeg.Encode(out, rgba, &opt)
carbocation
  • 8,806
  • 7
  • 25
  • 30
Aruna Herath
  • 6,241
  • 1
  • 40
  • 59
  • 4
    I really like this answer as it is very well formatted, explained each section individually, concise, used a lot of existing go functionality. – efel Mar 13 '16 at 04:23
  • efel, thanks. looks like I have confused between right and left ealiar. edited to fix :) – Aruna Herath Mar 18 '16 at 18:01
  • Hi I am trying to use this code a bit differently in my post, some input from you would be very helpful: https://stackoverflow.com/questions/55542284/how-do-i-concat-multiple-image-into-a-single-image – rak1n Apr 05 '19 at 19:43
  • Great answer! I made a few small changes and this worked with PNG images – lm5050 Jul 14 '23 at 08:20
9

Your life would be much easier if you make a few things into functions and create a struct to make sense of each pixel.

// Create a struct to deal with pixel
type Pixel struct {
    Point image.Point
    Color color.Color
}

// Keep it DRY so don't have to repeat opening file and decode
func OpenAndDecode(filepath string) (image.Image, string, error) {
    imgFile, err := os.Open(filepath)
    if err != nil {
        panic(err)
    }
    defer imgFile.Close()
    img, format, err := image.Decode(imgFile)
    if err != nil {
        panic(err)
    }
    return img, format, nil
}

// Decode image.Image's pixel data into []*Pixel
func DecodePixelsFromImage(img image.Image, offsetX, offsetY int) []*Pixel {
    pixels := []*Pixel{}
    for y := 0; y <= img.Bounds().Max.Y; y++ {
        for x := 0; x <= img.Bounds().Max.X; x++ {
            p := &Pixel{
                Point: image.Point{x + offsetX, y + offsetY},
                Color: img.At(x, y),
            }
            pixels = append(pixels, p)
        }
    }
    return pixels
}

func main() {
    img1, _, err := OpenAndDecode("makey.png")
    if err != nil {
        panic(err)
    }
    img2, _, err := OpenAndDecode("sample.jpg")
    if err != nil {
        panic(err)
    }
    // collect pixel data from each image
    pixels1 := DecodePixelsFromImage(img1, 0, 0)
    // the second image has a Y-offset of img1's max Y (appended at bottom)
    pixels2 := DecodePixelsFromImage(img2, 0, img1.Bounds().Max.Y)
    pixelSum := append(pixels1, pixels2...)

    // Set a new size for the new image equal to the max width
    // of bigger image and max height of two images combined
    newRect := image.Rectangle{
        Min: img1.Bounds().Min,
        Max: image.Point{
            X: img2.Bounds().Max.X,
            Y: img2.Bounds().Max.Y + img1.Bounds().Max.Y,
        },
    }
    finImage := image.NewRGBA(newRect)
    // This is the cool part, all you have to do is loop through
    // each Pixel and set the image's color on the go
    for _, px := range pixelSum {
            finImage.Set(
                px.Point.X,
                px.Point.Y,
                px.Color,
            )
    }
    draw.Draw(finImage, finImage.Bounds(), finImage, image.Point{0, 0}, draw.Src)

    // Create a new file and write to it
    out, err := os.Create("./output.png")
    if err != nil {
        panic(err)
        os.Exit(1)
    }
    err = png.Encode(out, finImage)
    if err != nil {
        panic(err)
        os.Exit(1)
    }
}
Pandemonium
  • 7,724
  • 3
  • 32
  • 51
  • 6
    A lot of work went into this answer, I like it as well. Very modular. – efel Mar 13 '16 at 04:24
  • This is a great answer. As the functions were correctly telling their roles in the entire operation, it became easy to understand – Pallav Jha Jul 26 '20 at 08:56
8

I built a library exactly for this purpose.

You can use it as follows;

import gim "github.com/ozankasikci/go-image-merge"

grids := []*gim.Grid{
    {ImageFilePath: "test1.jpg"},
    {ImageFilePath: "test2.png"},
}

// merge the images into a 2x1 grid
rgba, err := gim.New(grids, 2, 1).Merge()

// save the output to jpg or png
file, err := os.Create("file/path.jpg|png")
err = jpeg.Encode(file, rgba, &jpeg.Options{Quality: 80})

https://github.com/ozankasikci/go-image-merge

Ozan
  • 331
  • 4
  • 4
0

Resize images and concat them.

import (
    "fmt"
    "github.com/nfnt/resize"
    "image"
    "image/draw"
)

/**
w: resize weight
h: resize height
v: concat dim
images: list
*/
func Concat(w uint, h uint, v bool, images ...image.Image) *image.RGBA {

    var acc uint = 0
    if w == 0 {
        v = false
    } else if h == 0 {
        v = true
    }

    for i, img := range images {
        rimg := resize.Resize(w, h, img, resize.Bilinear)
        if v { // vertical concat, accumulate height
            acc += uint(rimg.Bounds().Dy())
        } else {
            acc += uint(rimg.Bounds().Dx())
        }
        images[i] = rimg
    }

    if v {
        h = acc
    } else {
        w = acc
    }

    r := image.Rectangle{image.Point{0, 0}, image.Point{int(w), int(h)}}
    rgba := image.NewRGBA(r)

    dx := 0
    dy := 0

    for _, img := range images {

        rec := img.Bounds()
        draw.Draw(rgba, image.Rect(dx, dy, dx+rec.Dx(), dy+rec.Dy()), img, image.Point{0, 0}, draw.Src)
        fmt.Println(image.Point{dx, dy})
        if v {
            dy += img.Bounds().Dy()
        } else {
            dx += img.Bounds().Dx()
        }
    }

    return rgba
}
Sailist
  • 134
  • 8