Golang Converting Image magick object to []byte - go

I am using the following code which fetch the object from Amazon s3 and after performing resizing and cropping. I want to store it on s3. But the problem is i am not able convert the mw (Image maigck object) to byte array. Which will be used for storing it on s3. Moreover in current method it uses jpeg.Encode. What if the image in .png or .gif format. How will we convert it to []byte?
Could you please also tell me how to evenly crop an image just passing the aspect ratio not cropping coordinates. imgStream.Crop((int)originalWidth, ((int)(originalWidth / masterAspectRatio)), Gravity.Center) like we do it in .net. Reason i am asking is there is no method in library which provides this flexibility.
s3Client := s3.New(session.New(), &aws.Config{Region: aws.String(region)})
params := &s3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(keyName),
}
out, err := s3Client.GetObject(params)
if err != nil {
log.Fatal(err)
}
img, err := ioutil.ReadAll(out.Body)
if err != nil {
log.Fatal(err)
}
mw := imagick.NewMagickWand()
err = mw.ReadImageBlob(img)
if err != nil {
log.Fatal(err)
}
//Perform resizing and cropping on mw object
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, mw, nil)
sendmw_s3 := buf.Bytes()
paramsPut := &s3.PutObjectInput{
Bucket: aws.String(masterBucketName),
Key: aws.String(keyName),
Body: bytes.NewReader(sendmw_s3),
}
resp, err := s3Client.PutObject(paramsPut)
if err != nil {
log.Fatal(err)
}
Error :
cannot use mw (type *imagick.MagickWand) as type image.Image in argument to jpeg.Encode:
*imagick.MagickWand does not implement image.Image (missing At method)

You need to use the func (mw *MagickWand) GetImageBlob() []byte function.
It returns a slice of bytes containing a complete encoded image for the current file format (JPEG, gif, PNG...).
The returned data can therefore be saved to disk, or sent to s3 as-is.
See https://gowalker.org/github.com/gographics/imagick/imagick#MagickWand_GetImageBlob for the documentation.

This question is actually two questions, and #SirDarius answered one of them, by suggesting the use of GetImageBlob(). You can also use SetImageFormat() to change the image format before generating the blob.
For the part about the crop, I am sure there are a bunch of ways to do this with ImageMagick. The way I have done it, to achieve a center crop is to first transform the image so that the smaller dimension fits into my desired target resolution. And then to crop away the parts that overflow.
// Create a new image where smallest dimension is fit
// and the rest overflows the dimensions
size := fmt.Sprintf("%dx%d^+0+0", w, h)
tx := wand.TransformImage("", size)
// Center Crop away the extra parts of the image, to perform
tx.SetImageGravity(imagick.GRAVITY_CENTER)
offsetX := -(int(w) - int(tx.GetImageWidth())) / 2
offsetY := -(int(h) - int(tx.GetImageHeight())) / 2
err := tx.ExtentImage(w, h, offsetX, offsetY)
...

Related

Golang image/gif EncodeAll have many black dots

I have many png images and want encode them to a gif animation.
These png images dont have any black dots ,but the gif result have many dots.
g := new(gif.GIF)
frames := len(images)
g.Image = make([]*image.Paletted, frames)
g.Delay = make([]int, frames)
eg := errgroup.Group{}
var cl color.Palette = palette.Plan9
for k, img := range images {
img := img
k := k
eg.Go(func() error {
Paletted := image.NewPaletted(img.Bounds(), cl)
draw.FloydSteinberg.Draw(Paletted, img.Bounds(), img, image.Point{})
g.Image[k] = Paletted
g.Delay[k] = deply
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
}
var buf bytes.Buffer
err := gif.EncodeAll(&buf, g)
origin png:
with num:
my png info:
File Type : PNG
File Type Extension : png
MIME Type : image/png
Bit Depth : 8
Color Type : RGB with Alpha
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
SRGB Rendering : Perceptual
Exif Byte Order : Big-endian (Motorola, MM)
Color Space : sRGB
gif with black dots:
used palgen.Generate(img, 256):
GIF uses a 256 color palette, whereas PNG typically is RGBA with at least 8 bits per color channel (and alpha). In your incomplete example code you use the predefined palette.Plan9 color palette. Picking at least the most dominant color in the origin PNG shows it's the RGB color #f08740. But there is no matching color in the Plan9 palette, so FloydSteinberg will have to dither using "nearest" colors from the Plan9 palette. This obviously doesn't work well.
You need to use an adapted palette to avoid dithering either at all or at least minimize it. As you are giving a non-minimal and incomplete example, I had to roll a minimal example myself that creates a custom palette for the GIF based on the only PNG source given (and please, do upload separate images next time, don't put everything into a single image, it makes things really inconvenient).
A quick google search reveals the Go module github.com/xyproto/palgen that does create a custom color.Palette based on an input image and with the specified numbers of colors; this module seems to be actively maintained and I had immediate success in using it:
img, _, err := image.Decode(f)
pal, err := palgen.Generate(img, 256)
The full example that produces for me a suitable GIF (gif.gif) without dithering, given a source PNG in source.png:
package main
import (
"image"
"image/draw"
"image/gif"
_ "image/png"
"os"
"github.com/xyproto/palgen"
)
func main() {
g := new(gif.GIF)
g.Image = make([]*image.Paletted, 1)
g.Delay = make([]int, 1)
f, err := os.Open("source.png")
if err != nil {
panic(err)
}
defer f.Close()
img, _, err := image.Decode(f)
pal, err := palgen.Generate(img, 256)
Paletted := image.NewPaletted(img.Bounds(), pal)
draw.FloydSteinberg.Draw(Paletted, img.Bounds(), img, image.Point{})
g.Image[0] = Paletted
g.Delay[0] = 100
out, err := os.Create("gif.gif")
if err != nil {
panic(err)
}
defer out.Close()
err = gif.EncodeAll(out, g)
if err != nil {
panic(err)
}
}
Source image source.png:
Final GIF gif.gif:
Use:
draw.Draw(Paletted,img.Bounds(),img,image.ZP, draw.Src)
Replace:
draw.FloydSteinberg.Draw(Paletted, img.Bounds(), img, image.Point{})

how to set DPI using Golang?

Golang image package is very handy to some extent but lack the support to set DPI of an image. I checked the file header of generated file, FF D8 FF DB which looks like jpeg raw. AFAIK, raw doesn't come with DPI like what jfif has. So here's my question, how to set DPI of the generated image? Or how to convert a raw to jfif, from which I know I can edit a specific bit of the file to set DPI? Previously I embedded an AdvancedBatchConverter executable in my app and used exec.Command(fmt.Sprintf("%s/AdvancedBatchConverter/abc.exe", cwd), outputFile, "/jfif", fmt.Sprintf("/convert=%s", jfifFileName))
to do the trick, but really, disgusted by it every time I looked at the code.
I believe you're looking for the exif values XResolution and YResolution
My understanding is the native jpeg encoder doesn't have any options for exif data.
https://github.com/dsoprea/go-exif will let you modify the exif data.
Additionally I believe if you first write the jpeg to a bytes.Buffer or similar and then append the exif you can do the entire thing in memory without flushing to disk first.
I hope that helps.
github.com/dsoprea/go-exif/v2 can reader and write exif data.
with other package github.com/dsoprea/go-jpeg-image-structure
here is code example . for write DPI(XResolution, YResolution) to Image.
import(
exif2 "github.com/dsoprea/go-exif/v2"
exifcommon "github.com/dsoprea/go-exif/v2/common"
jpegstructure "github.com/dsoprea/go-jpeg-image-structure"
)
func SetExifData(filepath string) error {
jmp := jpegstructure.NewJpegMediaParser()
intfc, err := jmp.ParseFile(filepath)
log.PanicIf(err)
sl := intfc.(*jpegstructure.SegmentList)
// Make sure we don't start out with EXIF data.
wasDropped, err := sl.DropExif()
log.PanicIf(err)
if wasDropped != true {
fmt.Printf("Expected the EXIF segment to be dropped, but it wasn't.")
}
im := exif2.NewIfdMapping()
err = exif2.LoadStandardIfds(im)
log.PanicIf(err)
ti := exif2.NewTagIndex()
rootIb := exif2.NewIfdBuilder(im, ti, exifcommon.IfdPathStandard, exifcommon.EncodeDefaultByteOrder)
err = rootIb.AddStandardWithName("XResolution", []exifcommon.Rational{{Numerator: uint32(96), Denominator: uint32(1)}})
log.PanicIf(err)
err = rootIb.AddStandardWithName("YResolution", []exifcommon.Rational{{Numerator: uint32(96), Denominator: uint32(1)}})
log.PanicIf(err)
err = sl.SetExif(rootIb)
log.PanicIf(err)
b := new(bytes.Buffer)
err = sl.Write(b)
log.PanicIf(err)
if err := ioutil.WriteFile(filepath, b.Bytes(), 0644); err != nil {
fmt.Printf("write file err: %v", err)
}
return nil
}

Golang some images are rotating on image upload

I am facing a rather weird problem, I am using Golang as a backend restful API and I upload images and resize them with Go . I have an app for I-phone that I am testing and if I upload an image using my real device the image gets saved in my s3 account sideways. For some reason resizing my image is rotating it, however if I upload the image from my Xcode IDE then the image gets saved correctly without rotation . I am thinking that maybe something is getting stripped however I have no idea what that could, my code is this
func UploadStreamImage(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
var buff bytes.Buffer
var result string
wg := sync.WaitGroup{}
print("Exoler-Streams")
wg.Add(1)
go func() {
defer wg.Done()
sess, _ := 's3 credentials'
svc := s3.New(sess)
file, handler, err := r.FormFile("file")
if err != nil {
fmt.Println("Error Uploading Image")
return
}
defer file.Close()
// resize image
img,err := imaging.Decode(file)
if err != nil {
print("Imaging Open error")
}
new_image := imaging.Resize(img, 300, 300, imaging.Lanczos)
var buf bytes.Buffer
err = imaging.Encode(&buf,new_image, imaging.JPEG)
if err != nil {
log.Println(err)
return
}
// end resize
r := bytes.NewReader(buf.Bytes())
read_file,err := ioutil.ReadAll(r)
if err != nil {
fmt.Println("Error Reading file")
}
// s3 specific code
}
The library I am using is this https://github.com/disintegration/imaging and I am just thinking that something is being stripped when uploading the image from my real device thus it is messing up the rotation . The code on the front-end is all the same for my real device and Xcode .
The image is not being rotated in the process, the original image was shown in some image view software in rotated mode depending on the image orientation tag (val x00112) in the Exif part of the file. When you strip the Exif part, as image package does, you lose that information and the image is shown in standard camera orientation format (landscape).
Old question but i thought i would add some extra information here to help anyone who is confused about what Exif is.
When an image is uploaded from a phone it stays in the orientation that it was taken in. However meta data is then added to that image under the key Orientation to tell you which way the image was taken. Phone libaries can then use this to show you the image the correct way around and so can you. It can be a number between 0 - 8. The only orientation numbers you will need to worry about is 3 (rotate 180), 6 (rotate 270) and 8 (rotate 90).
To get a hold of the meta information on an image you can use something like github.com/rwcarlsen/goexif/exif. Here is a snippet on how you can then get the rotation from that.
x, err := exif.Decode(openedFileExif)
var rotation float64 = 0
if err == nil {
orientationRaw, err := x.Get("Orientation")
if err == nil {
orientation := orientationRaw.String()
if orientation == "3" {
rotation = 180
} else if orientation == "6" {
rotation = 270
} else if orientation == "8" {
rotation = 90
}
}
}
Now that you have the rotation required you just need to rotate the image by that amount and you will have your normally oriented picture.
You can use something like github.com/disintegration/imaging for that. uploadedImage needs to be golang image.Image
if rotation != 0 {
uploadedImage = imaging.Rotate(uploadedImage, rotation, color.Gray{})
}

Golang: How to convert an image.image to uint16

I am trying to use the go-skeltrack library with some depth images I have (Not using freenect). For that I need to modify the provided example by replacing the kinect images by my own. For that I have to read an image and convert it later to an []uint16 variable. The code which I tried is:
file, err := os.Open("./images/4.png")
if err != nil {
fmt.Println("4.png file not found!")
os.Exit(1)
}
defer file.Close()
fileInfo, _ := file.Stat()
var size int64 = fileInfo.Size()
bytes := make([]byte, size)
// read file into bytes
buffer := bufio.NewReader(file)
_, err = buffer.Read(bytes)
integerImage := binary.BigEndian.Uint16(bytes)
onDepthFrame(integerImage)
Where onDepthFrame is a function which has the form
func onDepthFrame(depth []uint16).
But I am getting the following error while compiling:
./skeltrackOfflineImage.go:155: cannot use integerImage (type uint16) as type []uint16 in argument to onDepthFrame
Which of course refers to the fact that I generated a single integer instead of an array. I am quite confused about the way that Go data types conversion works. Please help!
Thanks in advance for your help.
Luis
binary.BigEndian.Uint16 converts two bytes (in a slice) to a 16-bit value using big endian byte order. If you want to convert bytes to a slice of uint16, you should use binary.Read:
// This reads 10 uint16s from file.
slice := make([]uint16, 10)
err := binary.Read(file, binary.BigEndian, slice)
It sounds like you're looking to get raw pixels. If that's the case, I don't recommend reading the file as binary directly. It means you would need to parse the file format yourself since image files contain more information than just the raw pixel values. There are already tools in the image package to deal with that.
This code should get you on the right track. It reads RGBA values, so it ends up with a 1D array of uint8's of length width * height * 4, since there are four values per pixel.
https://play.golang.org/p/WUgHQ3pRla
import (
"bufio"
"fmt"
"image"
"os"
// for decoding png files
_ "image/png"
)
// RGBA attempts to load an image from file and return the raw RGBA pixel values.
func RGBA(path string) ([]uint8, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
img, _, err := image.Decode(bufio.NewReader(file))
if err != nil {
return nil, err
}
switch trueim := img.(type) {
case *image.RGBA:
return trueim.Pix, nil
case *image.NRGBA:
return trueim.Pix, nil
}
return nil, fmt.Errorf("unhandled image format")
}
I'm not entirely sure where the uint16 values you need should come from, but presumably it's data per pixel, so the code should be very similar to this except the switch on trueim should likely check for something other than image.RGBA. Take a look at the other image types in https://golang.org/pkg/image

How to split gif into images

How can I split gif into images in go?
image/gif's DecodeAll return GIF, which contains an array of palette. But don't know how to convert each palette into an image?
Consider the following:
Frames can contain transparent pixels or areas, a good example is this image on wikipedia which (I guess) has one of these full-color blocks per frame and the rest of the frame transparent.
This introduces a problem for you: Especially with animated GIFs, that do not use multiple frames to create a true-colored static image, the frames that DecodeAll returns are not what you actually see if you, for example, open the image in your browser.
You'll have to process the image in the same way your browser would, i.e. leave the old frames on a kind of canvas and overpaint with the new frame. BUT this is not always true. GIF frames can, AFAIK, contain a disposal method, specifying how (or if?) you should dispose of the frame.
Anyways, to get to your point, the most simple approach that will also work in most cases is something like
import (
"errors"
"fmt"
"image"
"image/draw"
"image/gif"
"image/png"
"io"
"os"
)
// Decode reads and analyzes the given reader as a GIF image
func SplitAnimatedGIF(reader io.Reader) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Error while decoding: %s", r)
}
}()
gif, err := gif.DecodeAll(reader)
if err != nil {
return err
}
imgWidth, imgHeight := getGifDimensions(gif)
overpaintImage := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
draw.Draw(overpaintImage, overpaintImage.Bounds(), gif.Image[0], image.ZP, draw.Src)
for i, srcImg := range gif.Image {
draw.Draw(overpaintImage, overpaintImage.Bounds(), srcImg, image.ZP, draw.Over)
// save current frame "stack". This will overwrite an existing file with that name
file, err := os.Create(fmt.Sprintf("%s%d%s", "<some path>", i, ".png"))
if err != nil {
return err
}
err = png.Encode(file, overpaintImage)
if err != nil {
return err
}
file.Close()
}
return nil
}
func getGifDimensions(gif *gif.GIF) (x, y int) {
var lowestX int
var lowestY int
var highestX int
var highestY int
for _, img := range gif.Image {
if img.Rect.Min.X < lowestX {
lowestX = img.Rect.Min.X
}
if img.Rect.Min.Y < lowestY {
lowestY = img.Rect.Min.Y
}
if img.Rect.Max.X > highestX {
highestX = img.Rect.Max.X
}
if img.Rect.Max.Y > highestY {
highestY = img.Rect.Max.Y
}
}
return highestX - lowestX, highestY - lowestY
}
(untested, but should work)
Note that gif.DecodeAll can and will panic frequently, because a lot of the GIF images on the internet are somewhat broken. Your browser tries to decode them and will, for example, replace missing colors with black. image/gif will not do that, but panic instead. That's why we defer the recover.
Also, I used the getGifDimensions for a similar reason as stated above: single frames need not be what you see in your browser. In this case, the frames are just smaller than the complete image, that's why we have to iterate over all frames and get the "true" dimensions of the image.
If you really really want to do it right, you should probably read the GIF spec GIF87a, GIF89a and something like this article which is a lot easier to understand. From that, you should decide how to dispose of the frames and what to do with transparency while overpainting.
EDIT: Some of the effects mentioned earlier can be observed easily if you split some GIFs online, for example this or this - play around with "Ignore optimizations" and "Redraw every frame with details from previous frames" to see what I mean.
image.Image is an interface, and *image.Paletted implements the interface, so for example if you want to save every frame of a GIF into a PNG file, you can just encode every image:
for i, frame := range img.Image {
frameFile, err := os.OpenFile(fmt.Sprintf("%d.png", i+1), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
err = png.Encode(frameFile, frame)
if err != nil {
log.Fatal(err)
}
// Not using defer here because we're in a loop, not a function.
frameFile.Close()
}

Resources