feat: add EXIF thumbnail support for JPEG files (#1234)

This commit is contained in:
adrium
2021-03-29 11:40:00 +02:00
committed by GitHub
parent 4470d0a704
commit 7dd5b34d42
8 changed files with 135 additions and 1 deletions

View File

@@ -10,7 +10,10 @@ import (
"io"
"github.com/disintegration/imaging"
"github.com/dsoprea/go-exif/v3"
"github.com/marusama/semaphore/v2"
exifcommon "github.com/dsoprea/go-exif/v3/common"
)
// ErrUnsupportedFormat means the given image format is not supported.
@@ -152,6 +155,17 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
option(&config)
}
if config.quality == QualityLow && format == FormatJpeg {
thm, newWrappedReader, errThm := getEmbeddedThumbnail(wrappedReader)
wrappedReader = newWrappedReader
if errThm == nil {
_, err = out.Write(thm)
if err == nil {
return nil
}
}
}
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
if err != nil {
return err
@@ -183,3 +197,46 @@ func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
return format, io.MultiReader(buf, in), nil
}
func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
buf := &bytes.Buffer{}
r := io.TeeReader(in, buf)
wrappedReader := io.MultiReader(buf, in)
offset := 0
offsets := []int{12, 30}
head := make([]byte, 0xffff)
_, err := r.Read(head)
if err != nil {
return nil, wrappedReader, err
}
for _, offset = range offsets {
if _, err = exif.ParseExifHeader(head[offset:]); err == nil {
break
}
}
if err != nil {
return nil, wrappedReader, err
}
im, err := exifcommon.NewIfdMappingWithStandard()
if err != nil {
return nil, wrappedReader, err
}
_, index, err := exif.Collect(im, exif.NewTagIndex(), head[offset:])
if err != nil {
return nil, wrappedReader, err
}
ifd := index.RootIfd.NextIfd()
if ifd == nil {
return nil, wrappedReader, exif.ErrNoThumbnail
}
thm, err := ifd.Thumbnail()
return thm, wrappedReader, err
}

View File

@@ -216,6 +216,46 @@ func TestService_Resize(t *testing.T) {
},
matcher: sizeMatcher(100, 100),
},
"get thumbnail from file with APP0 JFIF": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/gray-sample.jpg")
},
matcher: sizeMatcher(125, 128),
},
"get thumbnail from file without APP0 JFIF": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/20130612_142406.jpg")
},
matcher: sizeMatcher(320, 240),
},
"resize from file without IFD1 thumbnail": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/IMG_2578.JPG")
},
matcher: sizeMatcher(100, 100),
},
"resize for higher quality levels": {
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
width: 100,
height: 100,
source: func(t *testing.T) afero.File {
t.Helper()
return openFile(t, "testdata/gray-sample.jpg")
},
matcher: sizeMatcher(100, 100),
},
"broken file": {
options: []Option{WithMode(ResizeModeFit)},
width: 100,
@@ -348,6 +388,15 @@ func newGrayBmp(t *testing.T, width, height int) afero.File {
return file
}
func openFile(t *testing.T, name string) afero.File {
appfs := afero.NewOsFs()
file, err := appfs.Open(name)
require.NoError(t, err)
return file
}
func TestService_FormatFromExtension(t *testing.T) {
testCases := map[string]struct {
ext string

BIN
img/testdata/20130612_142406.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
img/testdata/IMG_2578.JPG vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
img/testdata/gray-sample.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB