diff --git a/go/images/orientation.go b/go/images/orientation.go new file mode 100644 index 000000000..7c70f9ed7 --- /dev/null +++ b/go/images/orientation.go @@ -0,0 +1,190 @@ +package images + +import ( + "bytes" + "github.com/rwcarlsen/goexif/exif" + "image" + "image/draw" + "image/gif" + "image/jpeg" + "image/png" + "log" +) + +//many code is copied from http://camlistore.org/pkg/images/images.go +func FixJpgOrientation(ext string, data []byte) (oriented []byte) { + if ext != ".jpg" { + return data + } + ex, err := exif.Decode(bytes.NewReader(data)) + if err != nil { + return data + } + tag, err := ex.Get(exif.Orientation) + if err != nil { + return data + } + angle := 0 + flipMode := FlipDirection(0) + orient := tag.Int(0) + switch orient { + case topLeftSide: + // do nothing + return data + case topRightSide: + flipMode = 2 + case bottomRightSide: + angle = 180 + case bottomLeftSide: + angle = 180 + flipMode = 2 + case leftSideTop: + angle = -90 + flipMode = 2 + case rightSideTop: + angle = -90 + case rightSideBottom: + angle = 90 + flipMode = 2 + case leftSideBottom: + angle = 90 + } + + if srcImage, _, err := image.Decode(bytes.NewReader(data)); err == nil { + dstImage := flip(rotate(srcImage, angle), flipMode) + var buf bytes.Buffer + switch ext { + case ".png": + png.Encode(&buf, dstImage) + case ".jpg": + jpeg.Encode(&buf, dstImage, nil) + case ".gif": + gif.Encode(&buf, dstImage, nil) + } + return buf.Bytes() + } + + return data +} + +// Exif Orientation Tag values +// http://sylvana.net/jpegcrop/exif_orientation.html +const ( + topLeftSide = 1 + topRightSide = 2 + bottomRightSide = 3 + bottomLeftSide = 4 + leftSideTop = 5 + rightSideTop = 6 + rightSideBottom = 7 + leftSideBottom = 8 +) + +// The FlipDirection type is used by the Flip option in DecodeOpts +// to indicate in which direction to flip an image. +type FlipDirection int + +// FlipVertical and FlipHorizontal are two possible FlipDirections +// values to indicate in which direction an image will be flipped. +const ( + FlipVertical FlipDirection = 1 << iota + FlipHorizontal +) + +type DecodeOpts struct { + // Rotate specifies how to rotate the image. + // If nil, the image is rotated automatically based on EXIF metadata. + // If an int, Rotate is the number of degrees to rotate + // counter clockwise and must be one of 0, 90, -90, 180, or + // -180. + Rotate interface{} + + // Flip specifies how to flip the image. + // If nil, the image is flipped automatically based on EXIF metadata. + // Otherwise, Flip is a FlipDirection bitfield indicating how to flip. + Flip interface{} +} + +func rotate(im image.Image, angle int) image.Image { + var rotated *image.NRGBA + // trigonometric (i.e counter clock-wise) + switch angle { + case 90: + newH, newW := im.Bounds().Dx(), im.Bounds().Dy() + rotated = image.NewNRGBA(image.Rect(0, 0, newW, newH)) + for y := 0; y < newH; y++ { + for x := 0; x < newW; x++ { + rotated.Set(x, y, im.At(newH-1-y, x)) + } + } + case -90: + newH, newW := im.Bounds().Dx(), im.Bounds().Dy() + rotated = image.NewNRGBA(image.Rect(0, 0, newW, newH)) + for y := 0; y < newH; y++ { + for x := 0; x < newW; x++ { + rotated.Set(x, y, im.At(y, newW-1-x)) + } + } + case 180, -180: + newW, newH := im.Bounds().Dx(), im.Bounds().Dy() + rotated = image.NewNRGBA(image.Rect(0, 0, newW, newH)) + for y := 0; y < newH; y++ { + for x := 0; x < newW; x++ { + rotated.Set(x, y, im.At(newW-1-x, newH-1-y)) + } + } + default: + return im + } + return rotated +} + +// flip returns a flipped version of the image im, according to +// the direction(s) in dir. +// It may flip the imput im in place and return it, or it may allocate a +// new NRGBA (if im is an *image.YCbCr). +func flip(im image.Image, dir FlipDirection) image.Image { + if dir == 0 { + return im + } + ycbcr := false + var nrgba image.Image + dx, dy := im.Bounds().Dx(), im.Bounds().Dy() + di, ok := im.(draw.Image) + if !ok { + if _, ok := im.(*image.YCbCr); !ok { + log.Printf("failed to flip image: input does not satisfy draw.Image") + return im + } + // because YCbCr does not implement Set, we replace it with a new NRGBA + ycbcr = true + nrgba = image.NewNRGBA(image.Rect(0, 0, dx, dy)) + di, ok = nrgba.(draw.Image) + if !ok { + log.Print("failed to flip image: could not cast an NRGBA to a draw.Image") + return im + } + } + if dir&FlipHorizontal != 0 { + for y := 0; y < dy; y++ { + for x := 0; x < dx/2; x++ { + old := im.At(x, y) + di.Set(x, y, im.At(dx-1-x, y)) + di.Set(dx-1-x, y, old) + } + } + } + if dir&FlipVertical != 0 { + for y := 0; y < dy/2; y++ { + for x := 0; x < dx; x++ { + old := im.At(x, y) + di.Set(x, y, im.At(x, dy-1-y)) + di.Set(x, dy-1-y, old) + } + } + } + if ycbcr { + return nrgba + } + return im +} diff --git a/go/images/orientation_test.go b/go/images/orientation_test.go new file mode 100644 index 000000000..5c43e1999 --- /dev/null +++ b/go/images/orientation_test.go @@ -0,0 +1,17 @@ +package images + +import ( + "io/ioutil" + "testing" +) + +func TestXYZ(t *testing.T) { + fname := "sample1.jpg" + + dat, _ := ioutil.ReadFile(fname) + + fixed_data := FixJpgOrientation(".jpg", dat) + + ioutil.WriteFile("fixed1.jpg", fixed_data, 0644) + +} diff --git a/go/images/images.go b/go/images/resizing.go similarity index 96% rename from go/images/images.go rename to go/images/resizing.go index 9d2bb6101..f5077c1fd 100644 --- a/go/images/images.go +++ b/go/images/resizing.go @@ -9,9 +9,6 @@ import ( "image/png" ) -func FixJpgOrientation() { -} - func Resized(ext string, data []byte, width, height int) (resized []byte) { if width == 0 && height == 0 { return data diff --git a/go/images/sample1.jpg b/go/images/sample1.jpg new file mode 100644 index 000000000..991b59bac Binary files /dev/null and b/go/images/sample1.jpg differ