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
}
Related
I have an image that is stored in the filesystem. This file should be decoded to an image and then resized. I know how to resize it, but I can't decode the image. Whatever image path/image I insert in program, it results: image: unknown format.
I've already read all sites about this problem, but none of them did help me. This code represents my simplified program logic (I'd like to understand why this error occurs). In advance, thanks for your attention!
import (
"bufio"
"fmt"
"image"
"image/png"
_ "image/jpeg"
_ "image/png"
"log"
"os"
)
func main() {
file, err := os.Open(`D:\photos\img.png`)
if err != nil {
log.Fatal(err)
}
defer file.Close()
config, format, err := image.DecodeConfig(bufio.NewReader(file))
if err != nil {
log.Fatal(err)
}
fmt.Println(format, config.Height, config.Width, config.ColorModel)
decodedImg, format, err := image.Decode(bufio.NewReader(file)) // ERROR HERE
if err != nil {
log.Fatal(err)
}
fmt.Println(format,"decode")
outputFile, err := os.Create(`D:\photos\image.png`)
if err != nil {
log.Fatal(err)
}
defer outputFile.Close()
png.Encode(outputFile, decodedImg)
}
Output:
png 512 512 &{0x4ae340}
2020/07/11 09:37:10 image: unknown format
Both image.Decode and image.DecodeConfig consume the bytes from the passed-in io.Reader.
This means that after DecodeConfig is done, the position in the file is after the bytes already read. image.Decode then comes along with the same underlying file, expects to find the image header, but doesn't.
bufio.NewReader does not reset the position to the beginning of the file (because it can't, it only knows the underlying object is an io.Reader).
You have a few solutions (in order or personal preference):
seek back to the beginning of the file before calling image.Decode. eg: newOffset, err := file.Seek(0, 0)
don't use image.DecodeConfig (this might not be an option)
read the file into a []byte and use a bytes.Buffer
open the file again (not particularly efficient)
As a side note, you don't need to wrap the os.File object in a bufio.Reader, it already implements the io.Reader interface.
I am writing a golang script to send an image to the prediction engine of Google AutoML API.
It accepts most files using the code below, but certain .jpeg or .jpeg it returns error 500 saying invalid file. Mostly it works, but I can't figure out the exceptions. They are perfectly valid jpg's.
I am encoding the payload using EncodeToString.
Among other things, I have tried decoding it, saving it to a PNG, nothing seems to work. It doesn't like some images.
I wonder if I have an error in my method? Any help would be really appreciated. Thanks
PS the file saves to the filesystem and uploads to S3 just fine. It's just the encoding to a string when it goes to Google that it fails.
imgFile, err := os.Open(filename)
if err != nil {
fmt.Println(err)
}
img, fname, err := image.Decode(imgFile)
if err != nil {
fmt.Println(fname)
}
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, img, nil)
// Encode as base64.
imgBase64Str := base64.StdEncoding.EncodeToString(buf.Bytes())
defer imgFile.Close()
payload := fmt.Sprintf(`{"payload": {"image": {"imageBytes": "%v"},}}`, imgBase64Str)
// send as a byte
pay := bytes.NewBuffer([]byte(payload))
req, err := http.NewRequest(http.MethodPost, URL.String(), pay)
I believe I fixed it.
I looked in the Google docs again and for the speech to text (which is a different API) it says to do encode64 -w 0
So, looking in Go docs, it seems RawStdEncoding is right to use to replicate this behaviour, not StdEncoding
No image failures yet. Hope this helps someone else one day.
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{})
}
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
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)
...