func (ui *UIHandler) serveThumbnail(rw http.ResponseWriter, req *http.Request) { if ui.Storage == nil { http.Error(rw, "No BlobRoot configured", 500) return } fetchSeeker, err := ui.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } suffix := req.Header.Get("X-PrefixHandler-PathSuffix") m := thumbnailPattern.FindStringSubmatch(suffix) if m == nil { httputil.ErrorRouting(rw, req) return } query := req.URL.Query() width, err := strconv.Atoi(query.Get("mw")) if err != nil { http.Error(rw, "Invalid specified max width 'mw': "+err.String(), 500) return } height, err := strconv.Atoi(query.Get("mh")) if err != nil { http.Error(rw, "Invalid specified height 'mh': "+err.String(), 500) return } blobref := blobref.Parse(m[1]) if blobref == nil { http.Error(rw, "Invalid blobref", 400) return } filename := m[2] if len(filename) > 0 { filename = filename[1:] // remove leading slash } fr, err := schema.NewFileReader(fetchSeeker, blobref) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } var buf bytes.Buffer n, err := io.Copy(&buf, fr) i, format, err := image.Decode(&buf) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } b := i.Bounds() // only do downscaling, otherwise just serve the original image if width < b.Dx() || height < b.Dy() { 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 := width*2, height*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 width > height { width = b.Dx() * height / b.Dy() } else { height = b.Dy() * width / b.Dx() } i = resize.Resize(i, b, width, height) // 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 { http.Error(rw, "Can't serve file: "+err.String(), 500) return } } ct := "" switch format { case "jpeg": ct = "image/jpeg" default: ct = "image/png" } rw.Header().Set("Content-Type", ct) size := buf.Len() rw.Header().Set("Content-Length", fmt.Sprintf("%d", size)) n, err = io.Copy(rw, &buf) if err != nil { log.Printf("error serving thumbnail of file schema %s: %v", blobref, err) return } if n != int64(size) { log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d", blobref, n, size) return } }
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file *blobref.BlobRef) { if req.Method != "GET" && req.Method != "HEAD" { http.Error(rw, "Invalid method", 400) return } mw, mh := ih.MaxWidth, ih.MaxHeight if mw == 0 || mh == 0 || mw > 2000 || mh > 2000 { http.Error(rw, "bogus dimensions", 400) return } fetchSeeker, err := ih.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } fr, err := schema.NewFileReader(fetchSeeker, file) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } var buf bytes.Buffer n, err := io.Copy(&buf, fr) if err != nil { log.Printf("image resize: error reading image %s: %v", file, err) return } i, format, err := image.Decode(bytes.NewBuffer(buf.Bytes())) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } b := i.Bounds() 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 { http.Error(rw, "Can't serve file: "+err.String(), 500) return } } rw.Header().Set("Content-Type", imageContentTypeOfFormat(format)) size := buf.Len() rw.Header().Set("Content-Length", fmt.Sprintf("%d", size)) n, err = io.Copy(rw, &buf) if err != nil { log.Printf("error serving thumbnail of file schema %s: %v", file, err) return } if n != int64(size) { log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d", file, n, size) return } }