github.com/soreil/imager@v0.0.0-20160723145247-62b1c8bc4e6b/main.go (about) 1 // Package imager converts media types in to size optimised thumbnails 2 package imager 3 4 import ( 5 "bytes" 6 "errors" 7 "image" 8 "image/jpeg" 9 "io" 10 "sort" 11 12 // Import gif decoder 13 _ "image/gif" 14 15 // And our own decoders 16 _ "github.com/Soreil/pdf" 17 _ "github.com/Soreil/svg" 18 _ "github.com/Soreil/video/mkv" 19 _ "github.com/Soreil/video/mp4" 20 _ "github.com/Soreil/video/webm" 21 22 "github.com/nfnt/resize" 23 ) 24 25 // JPEGOptions specifies the options to use for encoding JPEG format image 26 // thumbnails. Should not be modified concurently with thumbnailing. 27 var JPEGOptions = jpeg.Options{Quality: jpeg.DefaultQuality} 28 29 // Thumb contains an io.Reader of the generated thumbnail and its width 30 // and height 31 type Thumb struct { 32 image.Rectangle 33 bytes.Buffer 34 } 35 36 // Scale resizes the image to max dimensions 37 // TODO(sjon): evaluate best resizing algorithm 38 func Scale(img image.Image, p image.Point) image.Image { 39 return resize.Thumbnail(uint(p.X), uint(p.Y), img, resize.Bilinear) 40 } 41 42 // Thumbnail makes a thumbnail out of a decodable media file. Sizes are the 43 // maximum dimensions of the thumbnail. Returns a Thumb of the resulting 44 // thumbnail, the format of the thumbnail, the dimensions of the source image 45 // and error, if any. 46 func Thumbnail(r io.Reader, s image.Point) ( 47 *Thumb, string, image.Rectangle, error, 48 ) { 49 img, imgString, err := image.Decode(r) 50 if err != nil { 51 return nil, "", image.Rectangle{}, err 52 } 53 54 srcDims := img.Bounds() 55 img = Scale(img, s) 56 format := autoSelectFormat(imgString) 57 out, err := Encode(img, format) 58 thumb := &Thumb{ 59 Rectangle: img.Bounds(), 60 Buffer: *out, 61 } 62 return thumb, format, srcDims, err 63 } 64 65 // Automatically select the output thumbnail image format 66 func autoSelectFormat(source string) string { 67 if source == "jpeg" { 68 return source 69 } 70 return "png" 71 } 72 73 // Encode encodes a given image.Image into the desired format. Currently only 74 // JPEG and PNG are supported. PNGs are lossily compressed, as per the 75 // PNGQuantization setting. 76 func Encode(img image.Image, format string) (*bytes.Buffer, error) { 77 var ( 78 out bytes.Buffer 79 err error 80 ) 81 switch format { 82 case "jpeg": 83 err = jpeg.Encode(&out, img, &JPEGOptions) 84 case "png": 85 err = compressPNG(&out, img) 86 default: 87 err = errors.New("Unsupported file type") 88 } 89 return &out, err 90 } 91 92 //Type for sort.Sort 93 type points []image.Point 94 95 func (p points) Len() int { 96 return len(p) 97 } 98 99 func (p points) Less(i, j int) bool { 100 return !p[i].In(image.Rectangle{Max: p[j]}) 101 } 102 103 func (p points) Swap(i, j int) { 104 p[i], p[j] = p[j], p[i] 105 } 106 107 // Thumbnails creates a thumbnail per size provided in sorted order from large 108 // to small to reduce the amount of computation required. Returns the sorted 109 // []Thumb of thumbnails, the format string of the thumbnails, dimensions of the 110 // source image and error, if any. 111 func Thumbnails(r io.Reader, sizes ...image.Point) ( 112 []*Thumb, string, image.Rectangle, error, 113 ) { 114 img, imgString, err := image.Decode(r) 115 if err != nil { 116 return nil, "", image.Rectangle{}, err 117 } 118 119 srcDims := img.Bounds() 120 121 //Make it so we have them in decreasing sized order 122 sort.Sort(points(sizes)) 123 scaled := make([]image.Image, len(sizes)) 124 for i, size := range sizes { 125 scaled[i] = Scale(img, size) 126 img = scaled[i] 127 } 128 129 thumbs := make([]*Thumb, len(sizes)) 130 format := autoSelectFormat(imgString) 131 for i, scale := range scaled { 132 buf, err := Encode(scale, format) 133 if err != nil { 134 return thumbs, format, srcDims, err 135 } 136 137 thumbs[i] = &Thumb{ 138 Rectangle: scale.Bounds(), 139 Buffer: *buf, 140 } 141 } 142 143 return thumbs, format, srcDims, err 144 }