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  }