// 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 }
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 }