Skip to content
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

Merged
merged 7 commits into from
Jun 25, 2020
Merged

Conversation

monkeyWie
Copy link
Contributor

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.

Copy link
Member

@o1egl o1egl left a 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.

if err != nil {
return errToStatus(err), err
}
dstImg := fitResizeImage(srcImg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 59 to 70
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
}
}()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return 0, nil
}

const maxSize = 1080
Copy link
Member

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

Copy link
Contributor Author

@monkeyWie monkeyWie Jun 10, 2020

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.

Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of the following changes?

  1. add /preview/thumb/file API for list icon,fixed size 128*128,like this:
    image
  2. change /thumbnail/file API to /preview/big/file for review, other unchanged.

Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

@monkeyWie
Copy link
Contributor Author

I tried to run it locally and I see a lot of errors in ui.

I found this error,it was caused by merge conflict,I will fix it.

}

if file.IsDir || file.Type != "image" {
return http.StatusNotFound, nil
Copy link
Member

@o1egl o1egl Jun 10, 2020

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?

Suggested change
return http.StatusNotFound, nil
return http.StatusNotImplemented, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

Copy link
Member

@o1egl o1egl left a 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use imaging.Fit()

Copy link
Contributor Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use imaging .Thumbnail()

Copy link
Member

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use path params

Suggested change
api.PathPrefix("/preview/thumb").Handler(monkey(previewThumbHandler, "/api/preview/thumb")).Methods("GET")
api.PathPrefix("/preview/{size}").Handler(monkey(previewThumbHandler, "/api/preview")).Methods("GET")

Copy link
Contributor Author

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?

Copy link
Member

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
Comment on lines 13 to 14
const sizeThumb = "thumb"
const sizeBig = "big"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please group constants

Suggested change
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Comment on lines 54 to 51
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))
}
Copy link
Member

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

@monkeyWie
Copy link
Contributor Author

@o1egl I have new submits,please review again.

Copy link
Member

@o1egl o1egl left a 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:], "/")
Copy link
Member

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 :)

Copy link
Contributor Author

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}")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

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.

Copy link
Member

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:.*}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it👍

http/preview.go Show resolved Hide resolved
@monkeyWie
Copy link
Contributor Author

@monkeyWie it still doesn't work. The ui is broken

Can't build frontend?

@o1egl
Copy link
Member

o1egl commented Jun 23, 2020

@monkeyWie it still doesn't work. The ui is broken

Can't build frontend?

UI is broken. It displays blank page.

@monkeyWie
Copy link
Contributor Author

@monkeyWie it still doesn't work. The ui is broken

Can't build frontend?

UI is broken. It displays blank page.

I test no problem,can you rebuild frontend again?

Copy link
Member

@o1egl o1egl left a 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 {
Copy link
Member

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 () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

@monkeyWie
Copy link
Contributor Author

@o1egl I think it's ok

@o1egl o1egl merged commit 6b0d49b into filebrowser:master Jun 25, 2020
@o1egl o1egl mentioned this pull request Jun 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants