MM-36295: Do not convert thumbnails/previews to jpeg for png images (#21230)
We were applying a white background to transparent images and converting them to jpegs. This was to make text be legible behind a black preview background. However, this has led to a poor user experience, as users rarely download the full image but always click on previews. Therefore, we need the previews to remain as pngs. To fix this, we just re-encode them as pngs instead of jpgs. ```release-note NONE ```
@@ -397,8 +397,8 @@ func TestUploadFiles(t *testing.T) {
|
|||||||
{
|
{
|
||||||
title: "Happy image thumbnail/preview 10",
|
title: "Happy image thumbnail/preview 10",
|
||||||
names: []string{"10000x1.png"},
|
names: []string{"10000x1.png"},
|
||||||
expectedImageThumbnailNames: []string{"10000x1_expected_thumb.jpeg"},
|
expectedImageThumbnailNames: []string{"10000x1_expected_thumb.png"},
|
||||||
expectedImagePreviewNames: []string{"10000x1_expected_preview.jpeg"},
|
expectedImagePreviewNames: []string{"10000x1_expected_preview.png"},
|
||||||
expectImage: true,
|
expectImage: true,
|
||||||
expectedImageWidths: []int{10000},
|
expectedImageWidths: []int{10000},
|
||||||
expectedImageHeights: []int{1},
|
expectedImageHeights: []int{1},
|
||||||
@@ -409,8 +409,8 @@ func TestUploadFiles(t *testing.T) {
|
|||||||
{
|
{
|
||||||
title: "Happy image thumbnail/preview 11",
|
title: "Happy image thumbnail/preview 11",
|
||||||
names: []string{"1x10000.png"},
|
names: []string{"1x10000.png"},
|
||||||
expectedImageThumbnailNames: []string{"1x10000_expected_thumb.jpeg"},
|
expectedImageThumbnailNames: []string{"1x10000_expected_thumb.png"},
|
||||||
expectedImagePreviewNames: []string{"1x10000_expected_preview.jpeg"},
|
expectedImagePreviewNames: []string{"1x10000_expected_preview.png"},
|
||||||
expectImage: true,
|
expectImage: true,
|
||||||
expectedImageWidths: []int{1},
|
expectedImageWidths: []int{1},
|
||||||
expectedImageHeights: []int{10000},
|
expectedImageHeights: []int{10000},
|
||||||
@@ -678,8 +678,12 @@ func TestUploadFiles(t *testing.T) {
|
|||||||
fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
|
fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
|
||||||
|
|
||||||
if tc.expectImage {
|
if tc.expectImage {
|
||||||
expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb.jpg", expectedDir, name)
|
// We convert all other image types to jpeg, except pngs.
|
||||||
expectedPreviewPath := fmt.Sprintf("%s/%s_preview.jpg", expectedDir, name)
|
if ext != ".png" {
|
||||||
|
ext = ".jpg"
|
||||||
|
}
|
||||||
|
expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb%s", expectedDir, name, ext)
|
||||||
|
expectedPreviewPath := fmt.Sprintf("%s/%s_preview%s", expectedDir, name, ext)
|
||||||
assert.Equal(t, dbInfo.ThumbnailPath, expectedThumbnailPath,
|
assert.Equal(t, dbInfo.ThumbnailPath, expectedThumbnailPath,
|
||||||
fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath))
|
fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath))
|
||||||
assert.Equal(t, dbInfo.PreviewPath, expectedPreviewPath,
|
assert.Equal(t, dbInfo.PreviewPath, expectedPreviewPath,
|
||||||
|
|||||||
92
app/file.go
@@ -266,8 +266,8 @@ func (a *App) getInfoForFilename(post *model.Post, teamID, channelID, userID, ol
|
|||||||
|
|
||||||
if info.IsImage() && !info.IsSvg() {
|
if info.IsImage() && !info.IsSvg() {
|
||||||
nameWithoutExtension := name[:strings.LastIndex(name, ".")]
|
nameWithoutExtension := name[:strings.LastIndex(name, ".")]
|
||||||
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
|
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview." + getFileExtFromMimeType(info.MimeType)
|
||||||
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
|
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(info.MimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
@@ -724,8 +724,8 @@ func (t *UploadFileTask) preprocessImage() *model.AppError {
|
|||||||
|
|
||||||
t.fileinfo.HasPreviewImage = true
|
t.fileinfo.HasPreviewImage = true
|
||||||
nameWithoutExtension := t.Name[:strings.LastIndex(t.Name, ".")]
|
nameWithoutExtension := t.Name[:strings.LastIndex(t.Name, ".")]
|
||||||
t.fileinfo.PreviewPath = t.pathPrefix() + nameWithoutExtension + "_preview.jpg"
|
t.fileinfo.PreviewPath = t.pathPrefix() + nameWithoutExtension + "_preview." + getFileExtFromMimeType(t.fileinfo.MimeType)
|
||||||
t.fileinfo.ThumbnailPath = t.pathPrefix() + nameWithoutExtension + "_thumb.jpg"
|
t.fileinfo.ThumbnailPath = t.pathPrefix() + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(t.fileinfo.MimeType)
|
||||||
|
|
||||||
// check the image orientation with goexif; consume the bytes we
|
// check the image orientation with goexif; consume the bytes we
|
||||||
// already have first, then keep Tee-ing from input.
|
// already have first, then keep Tee-ing from input.
|
||||||
@@ -770,20 +770,22 @@ func (t *UploadFileTask) postprocessImage(file io.Reader) {
|
|||||||
defer release()
|
defer release()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the background of a potentially-transparent png file as white
|
|
||||||
if imgType == "png" {
|
|
||||||
imaging.FillImageTransparency(decoded, image.White)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded = imaging.MakeImageUpright(decoded, t.imageOrientation)
|
decoded = imaging.MakeImageUpright(decoded, t.imageOrientation)
|
||||||
if decoded == nil {
|
if decoded == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJPEG := func(img image.Image, path string) {
|
writeImage := func(img image.Image, path string) {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
err := t.imgEncoder.EncodeJPEG(w, img, jpegEncQuality)
|
var err error
|
||||||
|
// It's okay to access imgType in a separate goroutine,
|
||||||
|
// because imgType is only written once and never written again.
|
||||||
|
if imgType == "png" {
|
||||||
|
err = t.imgEncoder.EncodePNG(w, img)
|
||||||
|
} else {
|
||||||
|
err = t.imgEncoder.EncodeJPEG(w, img, jpegEncQuality)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Error("Unable to encode image as jpeg", mlog.String("path", path), mlog.Err(err))
|
mlog.Error("Unable to encode image as jpeg", mlog.String("path", path), mlog.Err(err))
|
||||||
w.CloseWithError(err)
|
w.CloseWithError(err)
|
||||||
@@ -804,12 +806,12 @@ func (t *UploadFileTask) postprocessImage(file io.Reader) {
|
|||||||
// This is needed on mobile in case of animated GIFs.
|
// This is needed on mobile in case of animated GIFs.
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
writeJPEG(imaging.GenerateThumbnail(decoded, imageThumbnailWidth, imageThumbnailHeight), t.fileinfo.ThumbnailPath)
|
writeImage(imaging.GenerateThumbnail(decoded, imageThumbnailWidth, imageThumbnailHeight), t.fileinfo.ThumbnailPath)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
writeJPEG(imaging.GeneratePreview(decoded, imagePreviewWidth), t.fileinfo.PreviewPath)
|
writeImage(imaging.GeneratePreview(decoded, imagePreviewWidth), t.fileinfo.PreviewPath)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -889,8 +891,8 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
nameWithoutExtension := filename[:strings.LastIndex(filename, ".")]
|
nameWithoutExtension := filename[:strings.LastIndex(filename, ".")]
|
||||||
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
|
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview." + getFileExtFromMimeType(info.MimeType)
|
||||||
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
|
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(info.MimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
@@ -949,40 +951,33 @@ func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string,
|
|||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
|
|
||||||
for i := range fileData {
|
for i := range fileData {
|
||||||
img, release, err := prepareImage(a.ch.imgDecoder, bytes.NewReader(fileData[i]))
|
img, imgType, release, err := prepareImage(a.ch.imgDecoder, bytes.NewReader(fileData[i]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Debug("Failed to prepare image", mlog.Err(err))
|
mlog.Debug("Failed to prepare image", mlog.Err(err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
go func(img image.Image, path string) {
|
go func(img image.Image, imgType, path string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
a.generateThumbnailImage(img, path)
|
a.generateThumbnailImage(img, imgType, path)
|
||||||
}(img, thumbnailPathList[i])
|
}(img, imgType, thumbnailPathList[i])
|
||||||
|
|
||||||
go func(img image.Image, path string) {
|
go func(img image.Image, imgType, path string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
a.generatePreviewImage(img, path)
|
a.generatePreviewImage(img, imgType, path)
|
||||||
}(img, previewPathList[i])
|
}(img, imgType, previewPathList[i])
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
release()
|
release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareImage(imgDecoder *imaging.Decoder, imgData io.ReadSeeker) (img image.Image, release func(), err error) {
|
func prepareImage(imgDecoder *imaging.Decoder, imgData io.ReadSeeker) (img image.Image, imgType string, release func(), err error) {
|
||||||
// Decode image bytes into Image object
|
// Decode image bytes into Image object
|
||||||
var imgType string
|
|
||||||
img, imgType, release, err = imgDecoder.DecodeMemBounded(imgData)
|
img, imgType, release, err = imgDecoder.DecodeMemBounded(imgData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("prepareImage: failed to decode image: %w", err)
|
return nil, "", nil, fmt.Errorf("prepareImage: failed to decode image: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the background of a potentially-transparent png file as white
|
|
||||||
if imgType == "png" {
|
|
||||||
imaging.FillImageTransparency(img, image.White)
|
|
||||||
}
|
|
||||||
|
|
||||||
imgData.Seek(0, io.SeekStart)
|
imgData.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
// Flip the image to be upright
|
// Flip the image to be upright
|
||||||
@@ -992,15 +987,24 @@ func prepareImage(imgDecoder *imaging.Decoder, imgData io.ReadSeeker) (img image
|
|||||||
}
|
}
|
||||||
img = imaging.MakeImageUpright(img, orientation)
|
img = imaging.MakeImageUpright(img, orientation)
|
||||||
|
|
||||||
return img, release, nil
|
return img, imgType, release, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string) {
|
func (a *App) generateThumbnailImage(img image.Image, imgType, thumbnailPath string) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := a.ch.imgEncoder.EncodeJPEG(&buf, imaging.GenerateThumbnail(img, imageThumbnailWidth, imageThumbnailHeight), jpegEncQuality); err != nil {
|
|
||||||
|
thumb := imaging.GenerateThumbnail(img, imageThumbnailWidth, imageThumbnailHeight)
|
||||||
|
if imgType == "png" {
|
||||||
|
if err := a.ch.imgEncoder.EncodePNG(&buf, thumb); err != nil {
|
||||||
|
mlog.Error("Unable to encode image as png", mlog.String("path", thumbnailPath), mlog.Err(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := a.ch.imgEncoder.EncodeJPEG(&buf, thumb, jpegEncQuality); err != nil {
|
||||||
mlog.Error("Unable to encode image as jpeg", mlog.String("path", thumbnailPath), mlog.Err(err))
|
mlog.Error("Unable to encode image as jpeg", mlog.String("path", thumbnailPath), mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := a.WriteFile(&buf, thumbnailPath); err != nil {
|
if _, err := a.WriteFile(&buf, thumbnailPath); err != nil {
|
||||||
mlog.Error("Unable to upload thumbnail", mlog.String("path", thumbnailPath), mlog.Err(err))
|
mlog.Error("Unable to upload thumbnail", mlog.String("path", thumbnailPath), mlog.Err(err))
|
||||||
@@ -1008,14 +1012,21 @@ func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) generatePreviewImage(img image.Image, previewPath string) {
|
func (a *App) generatePreviewImage(img image.Image, imgType, previewPath string) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
preview := imaging.GeneratePreview(img, imagePreviewWidth)
|
|
||||||
|
|
||||||
|
preview := imaging.GeneratePreview(img, imagePreviewWidth)
|
||||||
|
if imgType == "png" {
|
||||||
|
if err := a.ch.imgEncoder.EncodePNG(&buf, preview); err != nil {
|
||||||
|
mlog.Error("Unable to encode image as preview png", mlog.Err(err), mlog.String("path", previewPath))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := a.ch.imgEncoder.EncodeJPEG(&buf, preview, jpegEncQuality); err != nil {
|
if err := a.ch.imgEncoder.EncodeJPEG(&buf, preview, jpegEncQuality); err != nil {
|
||||||
mlog.Error("Unable to encode image as preview jpg", mlog.Err(err), mlog.String("path", previewPath))
|
mlog.Error("Unable to encode image as preview jpg", mlog.Err(err), mlog.String("path", previewPath))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := a.WriteFile(&buf, previewPath); err != nil {
|
if _, err := a.WriteFile(&buf, previewPath); err != nil {
|
||||||
mlog.Error("Unable to upload preview", mlog.Err(err), mlog.String("path", previewPath))
|
mlog.Error("Unable to upload preview", mlog.Err(err), mlog.String("path", previewPath))
|
||||||
@@ -1033,7 +1044,7 @@ func (a *App) generateMiniPreview(fi *model.FileInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
img, release, err := prepareImage(a.ch.imgDecoder, file)
|
img, _, release, err := prepareImage(a.ch.imgDecoder, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Debug("generateMiniPreview: prepareImage failed", mlog.Err(err),
|
mlog.Debug("generateMiniPreview: prepareImage failed", mlog.Err(err),
|
||||||
mlog.String("fileinfo_id", fi.Id), mlog.String("channel_id", fi.ChannelId),
|
mlog.String("fileinfo_id", fi.Id), mlog.String("channel_id", fi.ChannelId),
|
||||||
@@ -1409,3 +1420,10 @@ func (a *App) getCloudFilesSizeLimit() (int64, *model.AppError) {
|
|||||||
|
|
||||||
return int64(math.Ceil(float64(*limits.Files.TotalStorage) / 8)), nil
|
return int64(math.Ceil(float64(*limits.Files.TotalStorage) / 8)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFileExtFromMimeType(mimeType string) string {
|
||||||
|
if mimeType == "image/png" {
|
||||||
|
return "png"
|
||||||
|
}
|
||||||
|
return "jpg"
|
||||||
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ func TestGenerateThumbnailImage(t *testing.T) {
|
|||||||
thumbnailPath := filepath.Join(dataPath, thumbnailName)
|
thumbnailPath := filepath.Join(dataPath, thumbnailName)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
th.App.generateThumbnailImage(img, thumbnailName)
|
th.App.generateThumbnailImage(img, "jpg", thumbnailName)
|
||||||
defer os.Remove(thumbnailPath)
|
defer os.Remove(thumbnailPath)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ func (a *App) SlackImport(c *request.Context, fileData multipart.File, fileSize
|
|||||||
GeneratePreviewImage: a.generatePreviewImage,
|
GeneratePreviewImage: a.generatePreviewImage,
|
||||||
InvalidateAllCaches: func() { a.ch.srv.InvalidateAllCaches() },
|
InvalidateAllCaches: func() { a.ch.srv.InvalidateAllCaches() },
|
||||||
MaxPostSize: func() int { return a.ch.srv.platform.MaxPostSize() },
|
MaxPostSize: func() int { return a.ch.srv.platform.MaxPostSize() },
|
||||||
PrepareImage: func(fileData []byte) (image.Image, func(), error) {
|
PrepareImage: func(fileData []byte) (image.Image, string, func(), error) {
|
||||||
img, release, err := prepareImage(a.ch.imgDecoder, bytes.NewReader(fileData))
|
img, imgType, release, err := prepareImage(a.ch.imgDecoder, bytes.NewReader(fileData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, "", nil, err
|
||||||
}
|
}
|
||||||
return img, release, err
|
return img, imgType, release, err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -298,8 +298,8 @@ func (a *App) UploadData(c *request.Context, us *model.UploadSession, rd io.Read
|
|||||||
}
|
}
|
||||||
|
|
||||||
nameWithoutExtension := info.Name[:strings.LastIndex(info.Name, ".")]
|
nameWithoutExtension := info.Name[:strings.LastIndex(info.Name, ".")]
|
||||||
info.PreviewPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_preview.jpg"
|
info.PreviewPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_preview." + getFileExtFromMimeType(info.MimeType)
|
||||||
info.ThumbnailPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_thumb.jpg"
|
info.ThumbnailPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(info.MimeType)
|
||||||
imgData, fileErr := a.ReadFile(uploadPath)
|
imgData, fileErr := a.ReadFile(uploadPath)
|
||||||
if fileErr != nil {
|
if fileErr != nil {
|
||||||
return nil, fileErr
|
return nil, fileErr
|
||||||
|
|||||||
@@ -91,11 +91,11 @@ type Actions struct {
|
|||||||
CreateGroupChannel func(request.CTX, []string) (*model.Channel, *model.AppError)
|
CreateGroupChannel func(request.CTX, []string) (*model.Channel, *model.AppError)
|
||||||
CreateChannel func(*model.Channel, bool) (*model.Channel, *model.AppError)
|
CreateChannel func(*model.Channel, bool) (*model.Channel, *model.AppError)
|
||||||
DoUploadFile func(time.Time, string, string, string, string, []byte) (*model.FileInfo, *model.AppError)
|
DoUploadFile func(time.Time, string, string, string, string, []byte) (*model.FileInfo, *model.AppError)
|
||||||
GenerateThumbnailImage func(image.Image, string)
|
GenerateThumbnailImage func(image.Image, string, string)
|
||||||
GeneratePreviewImage func(image.Image, string)
|
GeneratePreviewImage func(image.Image, string, string)
|
||||||
InvalidateAllCaches func()
|
InvalidateAllCaches func()
|
||||||
MaxPostSize func() int
|
MaxPostSize func() int
|
||||||
PrepareImage func(fileData []byte) (image.Image, func(), error)
|
PrepareImage func(fileData []byte) (image.Image, string, func(), error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SlackImporter is a service that allows to import slack dumps into mattermost
|
// SlackImporter is a service that allows to import slack dumps into mattermost
|
||||||
@@ -793,13 +793,13 @@ func (si *SlackImporter) oldImportFile(timestamp time.Time, file io.Reader, team
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fileInfo.IsImage() && !fileInfo.IsSvg() {
|
if fileInfo.IsImage() && !fileInfo.IsSvg() {
|
||||||
img, release, err := si.actions.PrepareImage(data)
|
img, imgType, release, err := si.actions.PrepareImage(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
si.actions.GenerateThumbnailImage(img, fileInfo.ThumbnailPath)
|
si.actions.GenerateThumbnailImage(img, imgType, fileInfo.ThumbnailPath)
|
||||||
si.actions.GeneratePreviewImage(img, fileInfo.PreviewPath)
|
si.actions.GeneratePreviewImage(img, imgType, fileInfo.PreviewPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileInfo, nil
|
return fileInfo, nil
|
||||||
|
|||||||
Двоичные данные
tests/10000x1_expected_preview.jpeg
|
До Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
tests/10000x1_expected_preview.png
Обычный файл
|
После Ширина: | Высота: | Размер: 92 B |
Двоичные данные
tests/10000x1_expected_thumb.jpeg
|
До Ширина: | Высота: | Размер: 628 B |
Двоичные данные
tests/10000x1_expected_thumb.png
Обычный файл
|
После Ширина: | Высота: | Размер: 76 B |
Двоичные данные
tests/1x10000_expected_preview.jpeg
|
До Ширина: | Высота: | Размер: 3.0 KiB |
Двоичные данные
tests/1x10000_expected_preview.png
Обычный файл
|
После Ширина: | Высота: | Размер: 121 B |
Двоичные данные
tests/1x10000_expected_thumb.jpeg
|
До Ширина: | Высота: | Размер: 624 B |
Двоичные данные
tests/1x10000_expected_thumb.png
Обычный файл
|
После Ширина: | Высота: | Размер: 79 B |