diff options
Diffstat (limited to 'weed/images')
| -rw-r--r-- | weed/images/orientation.go | 182 | ||||
| -rw-r--r-- | weed/images/orientation_test.go | 20 | ||||
| -rw-r--r-- | weed/images/preprocess.go | 29 |
3 files changed, 231 insertions, 0 deletions
diff --git a/weed/images/orientation.go b/weed/images/orientation.go new file mode 100644 index 000000000..a592a7d8b --- /dev/null +++ b/weed/images/orientation.go @@ -0,0 +1,182 @@ +package images + +import ( + "bytes" + "image" + "image/draw" + "image/jpeg" + "log" + + "github.com/seaweedfs/goexif/exif" +) + +//many code is copied from http://camlistore.org/pkg/images/images.go +func FixJpgOrientation(data []byte) (oriented []byte) { + 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, err := tag.Int(0) + if err != nil { + return data + } + 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 + jpeg.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/weed/images/orientation_test.go b/weed/images/orientation_test.go new file mode 100644 index 000000000..32fa38f76 --- /dev/null +++ b/weed/images/orientation_test.go @@ -0,0 +1,20 @@ +package images + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestXYZ(t *testing.T) { + fname := "sample1.jpg" + + dat, _ := ioutil.ReadFile(fname) + + fixed_data := FixJpgOrientation(dat) + + ioutil.WriteFile("fixed1.jpg", fixed_data, 0644) + + os.Remove("fixed1.jpg") + +} diff --git a/weed/images/preprocess.go b/weed/images/preprocess.go new file mode 100644 index 000000000..f6f3b554d --- /dev/null +++ b/weed/images/preprocess.go @@ -0,0 +1,29 @@ +package images + +import ( + "bytes" + "io" + "path/filepath" + "strings" +) + +/* +* Preprocess image files on client side. +* 1. possibly adjust the orientation +* 2. resize the image to a width or height limit +* 3. remove the exif data +* Call this function on any file uploaded to SeaweedFS +* + */ +func MaybePreprocessImage(filename string, data []byte, width, height int) (resized io.ReadSeeker, w int, h int) { + ext := filepath.Ext(filename) + ext = strings.ToLower(ext) + switch ext { + case ".png", ".gif": + return Resized(ext, bytes.NewReader(data), width, height, "") + case ".jpg", ".jpeg": + data = FixJpgOrientation(data) + return Resized(ext, bytes.NewReader(data), width, height, "") + } + return bytes.NewReader(data), 0, 0 +} |
