diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 5c5d59ef8..6148ed93e 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -858,7 +858,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { "static_url": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/small/`+instanceAccount.AvatarMediaAttachment.ID+`.webp",`+` "thumbnail_static_type": "image/webp", "thumbnail_description": "A bouncing little green peglin.", - "blurhash": "LE9as6M}4YtO%dRlWEt6Dmoxx?WC" + "blurhash": "LF9kG$RR4YtP%dR+V^t5D,oxx?WC" }`, string(instanceV2ThumbnailJson)) // double extra special bonus: now update the image description without changing the image diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 7f8cc2d87..e7f98d6d7 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -206,7 +206,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { Y: 0.5, }, }, *attachmentReply.Meta) - suite.Equal("LjCGfG#6RkRn_NvzRjWF?urqV@a$", *attachmentReply.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", *attachmentReply.Blurhash) suite.NotEmpty(attachmentReply.ID) suite.NotEmpty(attachmentReply.URL) suite.NotEmpty(attachmentReply.PreviewURL) @@ -291,7 +291,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { Y: 0.5, }, }, *attachmentReply.Meta) - suite.Equal("LjCGfG#6RkRn_NvzRjWF?urqV@a$", *attachmentReply.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", *attachmentReply.Blurhash) suite.NotEmpty(attachmentReply.ID) suite.Nil(attachmentReply.URL) suite.NotEmpty(attachmentReply.PreviewURL) diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go index 87777ea30..ff38176f1 100644 --- a/internal/media/manager_test.go +++ b/internal/media/manager_test.go @@ -276,7 +276,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcess() { suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) suite.Equal(22858, attachment.Thumbnail.FileSize) - suite.Equal("LjCGfG#6RkRn_NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -429,7 +429,7 @@ func (suite *ManagerTestSuite) TestSlothVineProcess() { suite.Equal("image/webp", attachment.Thumbnail.ContentType) suite.Equal(312453, attachment.File.FileSize) suite.Equal(5648, attachment.Thumbnail.FileSize) - suite.Equal("LhIrNMt6Nsj[t7ayW.j[_4WBsWkB", attachment.Blurhash) + suite.Equal("LfIYH~xtNskCxtfPW.kB_4aespof", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -489,7 +489,7 @@ func (suite *ManagerTestSuite) TestLongerMp4Process() { suite.Equal("image/webp", attachment.Thumbnail.ContentType) suite.Equal(109569, attachment.File.FileSize) suite.Equal(2976, attachment.Thumbnail.FileSize) - suite.Equal("L8QJfm~qD%_3_3D%t7RjM{j[ofRj", attachment.Blurhash) + suite.Equal("LJQJfm?bM{?b~qRjt7WBayWBofWB", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -549,7 +549,7 @@ func (suite *ManagerTestSuite) TestBirdnestMp4Process() { suite.Equal("image/webp", attachment.Thumbnail.ContentType) suite.Equal(1409625, attachment.File.FileSize) suite.Equal(14478, attachment.Thumbnail.FileSize) - suite.Equal("LKF~w1RjRO.99DM_RPaetkV?WCMw", attachment.Blurhash) + suite.Equal("LJF?FZV@RO.99DM_RPWAx]V?ayMw", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -657,7 +657,7 @@ func (suite *ManagerTestSuite) TestPngNoAlphaChannelProcess() { suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(17471, attachment.File.FileSize) suite.Equal(6446, attachment.Thumbnail.FileSize) - suite.Equal("LDQcrD%i-?aj%ho#M~RP~nf3~nt2", attachment.Blurhash) + suite.Equal("LFQT7e.A%O%4?co$M}M{_1W9~TxV", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -713,7 +713,7 @@ func (suite *ManagerTestSuite) TestPngAlphaChannelProcess() { suite.Equal("image/webp", attachment.Thumbnail.ContentType) suite.Equal(18832, attachment.File.FileSize) suite.Equal(3592, attachment.Thumbnail.FileSize) - suite.Equal("LBOW$@%i-rak%go#RSRP_1av~Ts+", attachment.Blurhash) + suite.Equal("LCONII.A%Oxw?co#M}M{_1ac~TxV", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -769,7 +769,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessWithCallback() { suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) suite.Equal(22858, attachment.Thumbnail.FileSize) - suite.Equal("LjCGfG#6RkRn_NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) @@ -847,7 +847,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessWithDiskStorage() { suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) suite.Equal(22858, attachment.Thumbnail.FileSize) - suite.Equal("LjCGfG#6RkRn_NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachment.ID) diff --git a/internal/media/thumbnail.go b/internal/media/thumbnail.go index 36ef24a01..a562dc2ad 100644 --- a/internal/media/thumbnail.go +++ b/internal/media/thumbnail.go @@ -34,6 +34,40 @@ "golang.org/x/image/webp" ) +const ( + maxThumbWidth = 512 + maxThumbHeight = 512 +) + +// thumbSize returns the dimensions to use for an input +// image of given width / height, for its outgoing thumbnail. +// This attempts to maintains the original image aspect ratio. +func thumbSize(width, height int, aspect float32) (int, int) { + + switch { + // Simplest case, within bounds! + case width < maxThumbWidth && + height < maxThumbHeight: + return width, height + + // Width is larger side. + case width > height: + // i.e. height = newWidth * (height / width) + height = int(float32(maxThumbWidth) / aspect) + return maxThumbWidth, height + + // Height is larger side. + case height > width: + // i.e. width = newHeight * (width / height) + width = int(float32(maxThumbHeight) * aspect) + return width, maxThumbHeight + + // Square. + default: + return maxThumbWidth, maxThumbHeight + } +} + // generateThumb generates a thumbnail for the // input file at path, resizing it to the given // dimensions and generating a blurhash if needed. @@ -229,11 +263,17 @@ func generateNativeThumb( img = imaging.Transverse(img) } - // Resize image to dimens. - img = imaging.Resize(img, - width, height, - imaging.Linear, - ) + // Resize image to dimens only if necessary. + if img.Bounds().Dx() > maxThumbWidth || + img.Bounds().Dy() > maxThumbHeight { + // Note: We could call "imaging.Fit" here + // but there's no point, as we've already + // calculated target dimensions beforehand. + img = imaging.Resize(img, + width, height, + imaging.Linear, + ) + } // Open output file at given path. outfile, err := os.Create(outpath) @@ -255,7 +295,7 @@ func generateNativeThumb( if needBlurhash { // for generating blurhashes, it's more cost effective to // lose detail since it's blurry, so make a tiny version. - tiny := imaging.Resize(img, 64, 64, imaging.NearestNeighbor) + tiny := imaging.Resize(img, 32, 0, imaging.NearestNeighbor) // Drop the larger image // ref as soon as possible @@ -294,7 +334,7 @@ func generateWebpBlurhash(filepath string) (string, error) { // for generating blurhashes, it's more cost effective to // lose detail since it's blurry, so make a tiny version. - tiny := imaging.Resize(img, 64, 64, imaging.NearestNeighbor) + tiny := imaging.Resize(img, 32, 0, imaging.NearestNeighbor) // Drop the larger image // ref as soon as possible diff --git a/internal/media/util.go b/internal/media/util.go index 17d396a0b..f743e3821 100644 --- a/internal/media/util.go +++ b/internal/media/util.go @@ -39,39 +39,6 @@ func getExtension(path string) string { return "" } -// thumbSize returns the dimensions to use for an input -// image of given width / height, for its outgoing thumbnail. -// This attempts to maintains the original image aspect ratio. -func thumbSize(width, height int, aspect float32) (int, int) { - const ( - maxThumbWidth = 512 - maxThumbHeight = 512 - ) - - switch { - // Simplest case, within bounds! - case width < maxThumbWidth && - height < maxThumbHeight: - return width, height - - // Width is larger side. - case width > height: - // i.e. height = newWidth * (height / width) - height = int(float32(maxThumbWidth) / aspect) - return maxThumbWidth, height - - // Height is larger side. - case height > width: - // i.e. width = newHeight * (width / height) - width = int(float32(maxThumbHeight) * aspect) - return width, maxThumbHeight - - // Square. - default: - return maxThumbWidth, maxThumbHeight - } -} - // getMimeType returns a suitable mimetype for file extension. func getMimeType(ext string) string { const defaultType = "application/octet-stream" diff --git a/testrig/media/thoughtsofdog-small.jpeg b/testrig/media/thoughtsofdog-small.jpeg index 911565a71..35c8f7e98 100644 Binary files a/testrig/media/thoughtsofdog-small.jpeg and b/testrig/media/thoughtsofdog-small.jpeg differ diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 943fb7396..26cc47f7d 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -1078,7 +1078,7 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { Thumbnail: gtsmodel.Thumbnail{ Path: "01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpeg", ContentType: "image/jpeg", - FileSize: 20394, + FileSize: 20395, URL: "http://localhost:8080/fileserver/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/small/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.webp", }, Avatar: util.Ptr(false), @@ -1124,7 +1124,7 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { Thumbnail: gtsmodel.Thumbnail{ Path: "062G5WYKY35KKD12EMSM3F8PJ8/attachment/small/01PFPMWK2FF0D9WMHEJHR07C3R.jpeg", ContentType: "image/webp", - FileSize: 20394, + FileSize: 20395, URL: "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/small/01PFPMWK2FF0D9WMHEJHR07C3R.webp", }, Avatar: util.Ptr(false),