Beispiel #1
0
// DecodeDownsample decodes JPEG data in r, down-sampling it by factor.
// If djpeg is not found, err is ErrDjpegNotFound and r is not read from.
// If the execution of djpeg, or decoding the resulting PNM fails, error will
// be of type DjpegFailedError.
func DecodeDownsample(r io.Reader, factor int) (image.Image, error) {
	if !Available() {
		return nil, ErrDjpegNotFound
	}
	switch factor {
	case 1, 2, 4, 8:
	default:
		return nil, fmt.Errorf("fastjpeg: unsupported sample factor %d", factor)
	}

	buf := new(bytes.Buffer)
	tr := io.TeeReader(r, buf)
	ic, format, err := image.DecodeConfig(tr)
	if err != nil {
		return nil, err
	}
	if format != "jpeg" {
		return nil, fmt.Errorf("fastjpeg: Unsupported format %q", format)
	}
	var bpp int
	switch ic.ColorModel {
	case color.YCbCrModel:
		bpp = 4 // JPEG will decode to RGB, and we'll expand inplace to RGBA.
	case color.GrayModel:
		bpp = 1
	default:
		return nil, fmt.Errorf("fastjpeg: Unsupported thumnbnail color model %T", ic.ColorModel)
	}
	args := []string{djpegBin, "-scale", fmt.Sprintf("1/%d", factor)}
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdin = readerutil.NewStatsReader(djpegBytesWrittenVar, io.MultiReader(buf, r))

	// Allocate space for the RGBA / Gray pixel data plus some extra for PNM
	// header info.  Explicitly allocate all the memory upfront to prevent
	// many smaller allocations.
	pixSize := ic.Width*ic.Height*bpp/factor/factor + 128
	w := bytes.NewBuffer(make([]byte, 0, pixSize))
	cmd.Stdout = w

	stderrW := new(bytes.Buffer)
	cmd.Stderr = stderrW
	if err := cmd.Run(); err != nil {
		djpegFailureVar.Add(1)
		return nil, DjpegFailedError{Err: fmt.Errorf("%v: %s", err, stderrW)}
	}
	djpegSuccessVar.Add(1)
	djpegBytesReadVar.Add(int64(w.Len()))
	m, err := readPNM(w)
	if err != nil {
		return m, DjpegFailedError{Err: err}
	}
	return m, nil
}
Beispiel #2
0
func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) {
	fr, err := ih.newFileReader(fileRef)
	if err != nil {
		return nil, err
	}
	defer fr.Close()

	sr := readerutil.NewStatsReader(imageBytesFetchedVar, fr)
	sr, conf, err := imageConfigFromReader(sr)
	if err != nil {
		return nil, err
	}

	// TODO(wathiede): build a size table keyed by conf.ColorModel for
	// common color models for a more exact size estimate.

	// This value is an estimate of the memory required to decode an image.
	// PNGs range from 1-64 bits per pixel (not all of which are supported by
	// the Go standard parser). JPEGs encoded in YCbCr 4:4:4 are 3 byte/pixel.
	// For all other JPEGs this is an overestimate.  For GIFs it is 3x larger
	// than needed.  How accurate this estimate is depends on the mix of
	// images being resized concurrently.
	ramSize := int64(conf.Width) * int64(conf.Height) * 3

	if err = ih.ResizeSem.Acquire(ramSize); err != nil {
		return nil, err
	}
	defer ih.ResizeSem.Release(ramSize)

	i, imConfig, err := images.Decode(sr, &images.DecodeOpts{
		MaxWidth:  ih.MaxWidth,
		MaxHeight: ih.MaxHeight,
	})
	if err != nil {
		return nil, err
	}
	b := i.Bounds()
	format := imConfig.Format

	isSquare := b.Dx() == b.Dy()
	if ih.Square && !isSquare {
		i = squareImage(i)
		b = i.Bounds()
	}

	// Encode as a new image
	var buf bytes.Buffer
	switch format {
	case "png":
		err = png.Encode(&buf, i)
	case "cr2":
		// Recompress CR2 files as JPEG
		format = "jpeg"
		fallthrough
	default:
		err = jpeg.Encode(&buf, i, &jpeg.Options{
			Quality: 90,
		})
	}
	if err != nil {
		return nil, err
	}

	return &formatAndImage{format: format, image: buf.Bytes()}, nil
}