func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { fr, err := schema.NewFileReader(ih.storageSeekFetcher(), fileRef) if err != nil { return nil, err } defer fr.Close() var buf bytes.Buffer scaleImageGateSlurp.Start() n, err := io.Copy(&buf, fr) scaleImageGateSlurp.Done() imageBytesFetchedVar.Add(n) if err != nil { return nil, fmt.Errorf("image resize: error reading image %s: %v", fileRef, err) } scaleImageGateResize.Start() defer scaleImageGateResize.Done() i, imConfig, err := images.Decode(bytes.NewReader(buf.Bytes()), &images.DecodeOpts{MaxWidth: ih.MaxWidth, MaxHeight: ih.MaxHeight}) if err != nil { return nil, err } b := i.Bounds() format := imConfig.Format useBytesUnchanged := !imConfig.Modified && format != "cr2" // always recompress CR2 files isSquare := b.Dx() == b.Dy() if ih.Square && !isSquare { useBytesUnchanged = false i = squareImage(i) b = i.Bounds() } if !useBytesUnchanged { // Encode as a new image buf.Reset() switch format { case "png": err = png.Encode(&buf, i) case "cr": // 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 }
func (ih *ImageHandler) scaleImage(buf *bytes.Buffer, file blob.Ref) (format string, err error) { fr, err := schema.NewFileReader(ih.storageSeekFetcher(), file) if err != nil { return format, err } defer fr.Close() _, err = io.Copy(buf, fr) if err != nil { return format, fmt.Errorf("image resize: error reading image %s: %v", file, err) } i, imConfig, err := images.Decode(bytes.NewReader(buf.Bytes()), &images.DecodeOpts{MaxWidth: ih.MaxWidth, MaxHeight: ih.MaxHeight}) if err != nil { return format, err } b := i.Bounds() format = imConfig.Format useBytesUnchanged := !imConfig.Modified && format != "cr2" // always recompress CR2 files isSquare := b.Dx() == b.Dy() if ih.Square && !isSquare { useBytesUnchanged = false i = squareImage(i) b = i.Bounds() } if !useBytesUnchanged { // Encode as a new image buf.Reset() switch format { case "png": err = png.Encode(buf, i) case "cr": // Recompress CR2 files as JPEG format = "jpeg" fallthrough default: err = jpeg.Encode(buf, i, nil) } if err != nil { return format, err } } return format, nil }
func (ih *ImageHandler) scaleImage(buf *bytes.Buffer, file *blobref.BlobRef) (format string, err error) { mw, mh := ih.MaxWidth, ih.MaxHeight fr, err := schema.NewFileReader(ih.storageSeekFetcher(), file) if err != nil { return format, err } _, err = io.Copy(buf, fr) if err != nil { return format, fmt.Errorf("image resize: error reading image %s: %v", file, err) } i, format, err := images.Decode(bytes.NewReader(buf.Bytes()), nil) if err != nil { return format, err } b := i.Bounds() // TODO(mpl): sort the useBytesUnchanged story out, // so that a rotation/flip is not being ignored // when there was no rescaling required. useBytesUnchanged := true isSquare := b.Dx() == b.Dy() if ih.Square && !isSquare { useBytesUnchanged = false i = squareImage(i) b = i.Bounds() } // only do downscaling, otherwise just serve the original image if mw < b.Dx() || mh < b.Dy() { useBytesUnchanged = false const huge = 2400 // If it's gigantic, it's more efficient to downsample first // and then resize; resizing will smooth out the roughness. // (trusting the moustachio guys on that one). if b.Dx() > huge || b.Dy() > huge { w, h := mw*2, mh*2 if b.Dx() > b.Dy() { w = b.Dx() * h / b.Dy() } else { h = b.Dy() * w / b.Dx() } i = resize.Resample(i, i.Bounds(), w, h) b = i.Bounds() } // conserve proportions. use the smallest of the two as the decisive one. if mw > mh { mw = b.Dx() * mh / b.Dy() } else { mh = b.Dy() * mw / b.Dx() } } if !useBytesUnchanged { i = resize.Resize(i, b, mw, mh) // Encode as a new image buf.Reset() switch format { case "jpeg": err = jpeg.Encode(buf, i, nil) default: err = png.Encode(buf, i) } if err != nil { return format, err } } return format, nil }
func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { fr, err := schema.NewFileReader(ih.Fetcher, fileRef) if err != nil { return nil, err } defer fr.Close() sr := types.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 }