-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support thumbnail image view #980
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to run it locally and I see a lot of errors in ui.
http/thumbnail.go
Outdated
if err != nil { | ||
return errToStatus(err), err | ||
} | ||
dstImg := fitResizeImage(srcImg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use https://pkg.go.dev/github.com/disintegration/imaging?tab=doc#FormatFromExtension with a fixed size
http/thumbnail.go
Outdated
err = func() error { | ||
switch file.Extension { | ||
case ".jpg", ".jpeg": | ||
return jpeg.Encode(w, dstImg, nil) | ||
case ".png": | ||
return png.Encode(w, dstImg) | ||
case ".gif": | ||
return gif.Encode(w, dstImg, nil) | ||
default: | ||
return errors.ErrNotExist | ||
} | ||
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http/thumbnail.go
Outdated
return 0, nil | ||
} | ||
|
||
const maxSize = 1080 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make sense to resize an image to 1080px and display it as a thumbnail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When resize the image resolution, can effectively reduce the size of the picture, for example reduce a 5MB picture to 500KB,so I choose 1080px as the reduce threshold.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the actual size of the thumbnail is 100x100 for example then why we should return 1080x1080 image for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact when the width and height < 1080,will return raw image,as below:
func fitResizeImage(srcImage image.Image) image.Image {
width := srcImage.Bounds().Dx()
height := srcImage.Bounds().Dy()
if width > maxSize && width > height {
width = maxSize
height = 0
} else if height > maxSize && height > width {
width = 0
height = maxSize
} else {
return srcImage
}
return imaging.Resize(srcImage, width, height, imaging.NearestNeighbor)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still do not understand why we should transfer additional data over the network and resize the image in the browser? Since the thumbnail size is fixed on ui, let's do the same on the backend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use single handler with path param /preview/{size}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course,and the most important thing is to preview the picture max size, or keep the 1080px?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it 1080px. User can always download original one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,I have a new commit,please review again.
I found this error,it was caused by merge conflict,I will fix it. |
http/thumbnail.go
Outdated
} | ||
|
||
if file.IsDir || file.Type != "image" { | ||
return http.StatusNotFound, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about returning StatusNotImplemented code?
return http.StatusNotFound, nil | |
return http.StatusNotImplemented, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok.
89d525a
to
9df4990
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rewrite it to something like that. With that we can keep this handler flexible to support different file types:
const (
sizeThumb = "thumb"
sizeBig = "big"
)
type imageProcessor func(img image.Image) (image.Image, error)
func previewHandler(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Download {
return http.StatusAccepted, nil
}
file, err := files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: r.URL.Path,
Modify: d.user.Perm.Modify,
Expand: true,
Checker: d,
})
if err != nil {
return errToStatus(err), err
}
size := mux.Vars(r)["size"]
switch file.Type {
case "image":
return handleImagePreview(w, file, size)
default:
return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", file.Type)
}
}
func handleImagePreview(w http.ResponseWriter, file *files.FileInfo, size string) (int, error) {
var imgProcessor imageProcessor
switch size {
case sizeBig:
imgProcessor = func(img image.Image) (image.Image, error) {
return imaging.Fit(img, 1080, 1080, imaging.Lanczos), nil
}
case sizeThumb:
imgProcessor = func(img image.Image) (image.Image, error) {
return imaging.Thumbnail(img, 128, 128, imaging.Box), nil
}
default:
return http.StatusBadRequest, fmt.Errorf("unsupported preview size %s", size)
}
fd, err := file.Fs.Open(file.Path)
if err != nil {
return errToStatus(err), err
}
defer fd.Close()
format, err := imaging.FormatFromExtension(file.Extension)
if err != nil {
return http.StatusNotImplemented, err
}
img, err := imaging.Decode(fd, imaging.AutoOrientation(true))
if err != nil {
return errToStatus(err), err
}
img, err = imgProcessor(img)
if err != nil {
return errToStatus(err), err
}
if imaging.Encode(w, img, format) != nil {
return errToStatus(err), err
}
return 0, nil
}
http/prewiew.go
Outdated
} | ||
var dstImage image.Image | ||
if size == sizeBig { | ||
dstImage = fitResizeImage(srcImg, 1080) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use imaging.Fit()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh,this pretty good!
http/prewiew.go
Outdated
if size == sizeBig { | ||
dstImage = fitResizeImage(srcImg, 1080) | ||
} else { | ||
dstImage = fitResizeImage(srcImg, 128) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use imaging .Thumbnail()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I would consider using one of those filters. We don't need super sharp image for thumbnails
- Linear
Bilinear resampling filter, produces a smooth output. Faster than cubic filters.
- Box
Simple and fast averaging filter appropriate for downscaling.
When upscaling it's similar to NearestNeighbor.
- NearestNeighbor
Fastest resampling filter, no antialiasing.
http/http.go
Outdated
@@ -59,6 +59,8 @@ func NewHandler(store *storage.Storage, server *settings.Server) (http.Handler, | |||
api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT") | |||
|
|||
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") | |||
api.PathPrefix("/preview/thumb").Handler(monkey(previewThumbHandler, "/api/preview/thumb")).Methods("GET") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use path params
api.PathPrefix("/preview/thumb").Handler(monkey(previewThumbHandler, "/api/preview/thumb")).Methods("GET") | |
api.PathPrefix("/preview/{size}").Handler(monkey(previewThumbHandler, "/api/preview")).Methods("GET") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If use path params,I need trim r.URL.Path
,like /thumb/path/file
trim to /path/file
,do you have any good suggestions for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can define filepath as another param.
http/prewiew.go
Outdated
const sizeThumb = "thumb" | ||
const sizeBig = "big" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please group constants
const sizeThumb = "thumb" | |
const sizeBig = "big" | |
const ( | |
sizeThumb = "thumb" | |
sizeBig = "big" | |
) |
http/prewiew.go
Outdated
}) | ||
} | ||
|
||
func previewFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo, size string) (int, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func previewFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo, size string) (int, error) { | |
func imagePreviewHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo, size string) (int, error) { |
http/prewiew.go
Outdated
if r.URL.Query().Get("inline") == "true" { | ||
w.Header().Set("Content-Disposition", "inline") | ||
} else { | ||
// As per RFC6266 section 4.3 | ||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(file.Name)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be done just before encoding process
e398fbb
to
4c5fb06
Compare
@o1egl I have new submits,please review again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@monkeyWie it still doesn't work. The ui is broken
http/preview.go
Outdated
} | ||
|
||
// Resolve file path from URL | ||
path := "/" + strings.Join(strings.Split(r.URL.Path, "/")[2:], "/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not to use the path as a variable like the size? Now it looks like a real magic :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean like this?
api.PathPrefix("/preview/{size}/{path}")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way doesn't work when the path like photos/xxx.jpg
,the request URI is /preview/big/photos/xxx.jpg
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use regexp for that /preview/{size}{path:.*}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got it👍
Can't build frontend? |
UI is broken. It displays blank page. |
d5bf839
to
07b2302
Compare
I test no problem,can you rebuild frontend again? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor changes
thumbnail () { | ||
if (this.req.type === 'image') { | ||
return `${baseURL}/api/preview/big${this.req.path}?auth=${this.jwt}` | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else is redundant
@@ -86,8 +86,15 @@ export default { | |||
download () { | |||
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}` | |||
}, | |||
thumbnail () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thumbnail () { | |
previewUrl() { |
http/raw.go
Outdated
@@ -22,7 +22,7 @@ func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]strin | |||
fileSlice = append(fileSlice, f.Path) | |||
} else { | |||
for _, name := range names { | |||
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:shadow | |||
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) // nolint:shadow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change is not correct. there should not be a space in nolint comment
http/raw.go
Outdated
@@ -35,7 +35,7 @@ func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]strin | |||
return fileSlice, nil | |||
} | |||
|
|||
//nolint: goconst | |||
// nolint: goconst |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@o1egl I think it's ok |
Reopen PR,now is using
github.com/disintegration/imaging
lib to handle image,and refactor code.Regarding the frontend code,I run
npm run lint
to fix the errors detected by eslint.