From d6182a4ea1ab0719252ac189f445e0295efbe631 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 4 Mar 2022 19:13:48 +0100 Subject: [PATCH] generate animated thumbnails for animated gifs --- .../services/thumbnails/v0/thumbnails.pb.go | 82 ++++++++++--------- .../thumbnails/v0/thumbnails.swagger.json | 3 +- .../services/thumbnails/v0/thumbnails.proto | 1 + thumbnails/pkg/preprocessor/preprocessor.go | 19 ++++- thumbnails/pkg/service/v0/service.go | 20 +++-- thumbnails/pkg/thumbnail/encoding.go | 70 +++++++++++++--- thumbnails/pkg/thumbnail/encoding_test.go | 2 +- thumbnails/pkg/thumbnail/generator.go | 65 +++++++++++++++ thumbnails/pkg/thumbnail/thumbnail.go | 32 +++++--- thumbnails/pkg/thumbnail/thumbnail_test.go | 4 +- webdav/pkg/service/v0/service.go | 4 +- 11 files changed, 223 insertions(+), 79 deletions(-) create mode 100644 thumbnails/pkg/thumbnail/generator.go diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go index cb2dd1d8639..ac70eb222e8 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go @@ -28,6 +28,7 @@ type GetThumbnailRequest_ThumbnailType int32 const ( GetThumbnailRequest_PNG GetThumbnailRequest_ThumbnailType = 0 // Represents PNG type GetThumbnailRequest_JPG GetThumbnailRequest_ThumbnailType = 1 // Represents JPG type + GetThumbnailRequest_GIF GetThumbnailRequest_ThumbnailType = 2 // Represents GIF type ) // Enum value maps for GetThumbnailRequest_ThumbnailType. @@ -35,10 +36,12 @@ var ( GetThumbnailRequest_ThumbnailType_name = map[int32]string{ 0: "PNG", 1: "JPG", + 2: "GIF", } GetThumbnailRequest_ThumbnailType_value = map[string]int32{ "PNG": 0, "JPG": 1, + "GIF": 2, } ) @@ -257,7 +260,7 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8e, 0x03, 0x0a, 0x13, 0x47, 0x65, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x03, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x65, 0x0a, @@ -279,47 +282,48 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x21, 0x0a, + 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x2a, 0x0a, 0x0d, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x50, 0x47, 0x10, 0x01, - 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, - 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, - 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xeb, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x12, 0x07, 0x0a, 0x03, 0x47, 0x49, 0x46, 0x10, 0x02, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, + 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, + 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, + 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, + 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, + 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, + 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, + 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, + 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0xeb, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, + 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, + 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, + 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, + 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, + 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, + 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, - 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, - 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, - 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, - 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, - 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, - 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, - 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, - 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, - 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, - 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, - 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, - 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, - 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, + 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, + 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, + 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, + 0x12, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json index f79a4f11908..aef36cc0953 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json @@ -34,7 +34,8 @@ "type": "string", "enum": [ "PNG", - "JPG" + "JPG", + "GIF" ], "default": "PNG", "description": "The file types to which the thumbnail can get encoded to." diff --git a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto index 0b7eae51715..c8141aedba3 100644 --- a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto +++ b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto @@ -45,6 +45,7 @@ message GetThumbnailRequest { enum ThumbnailType { PNG = 0; // Represents PNG type JPG = 1; // Represents JPG type + GIF = 2; // Represents GIF type } // The type to which the thumbnail should get encoded to. ThumbnailType thumbnail_type = 2; diff --git a/thumbnails/pkg/preprocessor/preprocessor.go b/thumbnails/pkg/preprocessor/preprocessor.go index f521967d57b..d0b1acc6f93 100644 --- a/thumbnails/pkg/preprocessor/preprocessor.go +++ b/thumbnails/pkg/preprocessor/preprocessor.go @@ -4,6 +4,7 @@ import ( "bufio" "image" "image/draw" + "image/gif" "io" "math" "mime" @@ -16,12 +17,12 @@ import ( ) type FileConverter interface { - Convert(r io.Reader) (image.Image, error) + Convert(r io.Reader) (interface{}, error) } type ImageDecoder struct{} -func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) { +func (i ImageDecoder) Convert(r io.Reader) (interface{}, error) { img, _, err := image.Decode(r) if err != nil { return nil, errors.Wrap(err, `could not decode the image`) @@ -29,11 +30,21 @@ func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) { return img, nil } +type GifDecoder struct{} + +func (i GifDecoder) Convert(r io.Reader) (interface{}, error) { + img, err := gif.DecodeAll(r) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil +} + type TxtToImageConverter struct { fontLoader *FontLoader } -func (t TxtToImageConverter) Convert(r io.Reader) (image.Image, error) { +func (t TxtToImageConverter) Convert(r io.Reader) (interface{}, error) { img := image.NewRGBA(image.Rect(0, 0, 640, 480)) imgBounds := img.Bounds() @@ -183,6 +194,8 @@ func ForType(mimeType string, opts map[string]interface{}) FileConverter { return TxtToImageConverter{ fontLoader: fontLoader, } + case "image/gif": + return GifDecoder{} default: return ImageDecoder{} } diff --git a/thumbnails/pkg/service/v0/service.go b/thumbnails/pkg/service/v0/service.go index 87c638f2255..32f9688ceeb 100644 --- a/thumbnails/pkg/service/v0/service.go +++ b/thumbnails/pkg/service/v0/service.go @@ -71,19 +71,23 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") return nil } - encoder := thumbnail.EncoderForType(req.ThumbnailType.String()) - if encoder == nil { + generator, err := thumbnail.GeneratorForType(req.ThumbnailType.String()) + if err != nil { + g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") + return nil + } + encoder, err := thumbnail.EncoderForType(req.ThumbnailType.String()) + if err != nil { g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") return nil } var thumb []byte - var err error switch { case req.GetWebdavSource() != nil: - thumb, err = g.handleWebdavSource(ctx, req, encoder) + thumb, err = g.handleWebdavSource(ctx, req, generator, encoder) case req.GetCs3Source() != nil: - thumb, err = g.handleCS3Source(ctx, req, encoder) + thumb, err = g.handleCS3Source(ctx, req, generator, encoder) default: g.logger.Error().Msg("no image source provided") return merrors.BadRequest(g.serviceID, "image source is missing") @@ -97,7 +101,7 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb return nil } -func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { src := req.GetCs3Source() sRes, err := g.stat(src.Path, src.Authorization) if err != nil { @@ -106,6 +110,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh tr := thumbnail.Request{ Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, Encoder: encoder, Checksum: sRes.GetInfo().GetChecksum().GetSum(), } @@ -136,7 +141,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh return thumb, nil } -func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { src := req.GetWebdavSource() imgURL, err := url.Parse(src.Url) if err != nil { @@ -182,6 +187,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge } tr := thumbnail.Request{ Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, Encoder: encoder, Checksum: sRes.GetInfo().GetChecksum().GetSum(), } diff --git a/thumbnails/pkg/thumbnail/encoding.go b/thumbnails/pkg/thumbnail/encoding.go index b253ca3b5fb..d9c43d2eceb 100644 --- a/thumbnails/pkg/thumbnail/encoding.go +++ b/thumbnails/pkg/thumbnail/encoding.go @@ -1,17 +1,33 @@ package thumbnail import ( + "errors" "image" + "image/gif" "image/jpeg" "image/png" "io" "strings" ) +const ( + typePng = "png" + typeJpg = "jpg" + typeJpeg = "jpeg" + typeGif = "gif" +) + +var ( + // ErrInvalidType represents the error when a type can't be encoded. + ErrInvalidType = errors.New("can't encode this type") + // ErrNoEncoderForType represents the error when an encoder couldn't be found for a type. + ErrNoEncoderForType = errors.New("no encoder for this type found") +) + // Encoder encodes the thumbnail to a specific format. type Encoder interface { // Encode encodes the image to a format. - Encode(io.Writer, image.Image) error + Encode(io.Writer, interface{}) error // Types returns the formats suffixes. Types() []string // MimeType returns the mimetype used by the encoder. @@ -22,13 +38,17 @@ type Encoder interface { type PngEncoder struct{} // Encode encodes to png format -func (e PngEncoder) Encode(w io.Writer, i image.Image) error { - return png.Encode(w, i) +func (e PngEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return ErrInvalidType + } + return png.Encode(w, m) } // Types returns the png suffix func (e PngEncoder) Types() []string { - return []string{"png"} + return []string{typePng} } // MimeType returns the mimetype for png files. @@ -40,13 +60,17 @@ func (e PngEncoder) MimeType() string { type JpegEncoder struct{} // Encode encodes to jpg -func (e JpegEncoder) Encode(w io.Writer, i image.Image) error { - return jpeg.Encode(w, i, nil) +func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return ErrInvalidType + } + return jpeg.Encode(w, m, nil) } // Types returns the jpg suffixes. func (e JpegEncoder) Types() []string { - return []string{"jpeg", "jpg"} + return []string{typeJpeg, typeJpg} } // MimeType returns the mimetype for jpg files. @@ -54,15 +78,35 @@ func (e JpegEncoder) MimeType() string { return "image/jpeg" } +type GifEncoder struct{} + +func (e GifEncoder) Encode(w io.Writer, img interface{}) error { + g, ok := img.(*gif.GIF) + if !ok { + return ErrInvalidType + } + return gif.EncodeAll(w, g) +} + +func (e GifEncoder) Types() []string { + return []string{typeGif} +} + +func (e GifEncoder) MimeType() string { + return "image/gif" +} + // EncoderForType returns the encoder for a given file type // or nil if the type is not supported. -func EncoderForType(fileType string) Encoder { +func EncoderForType(fileType string) (Encoder, error) { switch strings.ToLower(fileType) { - case "png": - return PngEncoder{} - case "jpg", "jpeg": - return JpegEncoder{} + case typePng: + return PngEncoder{}, nil + case typeJpg, typeJpeg: + return JpegEncoder{}, nil + case typeGif: + return GifEncoder{}, nil default: - return nil + return nil, ErrNoEncoderForType } } diff --git a/thumbnails/pkg/thumbnail/encoding_test.go b/thumbnails/pkg/thumbnail/encoding_test.go index 1dfbef60aa6..6dcce72a621 100644 --- a/thumbnails/pkg/thumbnail/encoding_test.go +++ b/thumbnails/pkg/thumbnail/encoding_test.go @@ -14,7 +14,7 @@ func TestEncoderForType(t *testing.T) { } for k, v := range table { - e := EncoderForType(k) + e, _ := EncoderForType(k) if e != v { t.Fail() } diff --git a/thumbnails/pkg/thumbnail/generator.go b/thumbnails/pkg/thumbnail/generator.go new file mode 100644 index 00000000000..6bea9b87a3b --- /dev/null +++ b/thumbnails/pkg/thumbnail/generator.go @@ -0,0 +1,65 @@ +package thumbnail + +import ( + "errors" + "image" + "image/draw" + "image/gif" + "strings" + + "github.com/disintegration/imaging" +) + +var ( + // ErrInvalidType represents the error when a type can't be encoded. + ErrInvalidType2 = errors.New("can't encode this type") + // ErrNoGeneratorForType represents the error when no generator could be found for a type. + ErrNoGeneratorForType = errors.New("no generator for this type found") +) + +type Generator interface { + GenerateThumbnail(image.Rectangle, interface{}) (interface{}, error) +} + +type SimpleGenerator struct{} + +func (g SimpleGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(image.Image) + if !ok { + return nil, ErrInvalidType2 + } + + return imaging.Thumbnail(m, size.Dx(), size.Dy(), imaging.Lanczos), nil +} + +type GifGenerator struct{} + +func (g GifGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(*gif.GIF) + if !ok { + return nil, ErrInvalidType2 + } + var bounds image.Rectangle + for i := range m.Image { + img := imaging.Resize(m.Image[i], size.Dx(), size.Dy(), imaging.Lanczos) + bounds = image.Rect(0, 0, size.Dx(), size.Dy()) + m.Image[i] = image.NewPaletted(bounds, m.Image[i].Palette) + draw.Draw(m.Image[i], bounds, img, image.Pt(0, 0), draw.Src) + } + m.Config.Height = bounds.Dy() + m.Config.Width = bounds.Dx() + return m, nil +} + +// GeneratorForType returns the generator for a given file type +// or nil if the type is not supported. +func GeneratorForType(fileType string) (Generator, error) { + switch strings.ToLower(fileType) { + case typePng, typeJpg, typeJpeg: + return SimpleGenerator{}, nil + case typeGif: + return GifGenerator{}, nil + default: + return nil, ErrNoEncoderForType + } +} diff --git a/thumbnails/pkg/thumbnail/thumbnail.go b/thumbnails/pkg/thumbnail/thumbnail.go index 815c8928a33..ceeca03f410 100644 --- a/thumbnails/pkg/thumbnail/thumbnail.go +++ b/thumbnails/pkg/thumbnail/thumbnail.go @@ -2,12 +2,13 @@ package thumbnail import ( "bytes" - "github.com/disintegration/imaging" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" "image" + "image/gif" "mime" "strings" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" ) var ( @@ -24,13 +25,14 @@ var ( type Request struct { Resolution image.Rectangle Encoder Encoder + Generator Generator Checksum string } // Manager is responsible for generating thumbnails type Manager interface { // Generate will return a thumbnail for a file - Generate(Request, image.Image) ([]byte, error) + Generate(Request, interface{}) ([]byte, error) // Get loads the thumbnail from the storage. // It will return nil if no image is stored for the given context. Get(Request) ([]byte, bool) @@ -54,12 +56,22 @@ type SimpleManager struct { // Generate creates a thumbnail and stores it. // The created thumbnail is also being returned. -func (s SimpleManager) Generate(r Request, img image.Image) ([]byte, error) { - match := s.resolutions.ClosestMatch(r.Resolution, img.Bounds()) - thumbnail := s.generate(match, img) +func (s SimpleManager) Generate(r Request, img interface{}) ([]byte, error) { + var match image.Rectangle + switch m := img.(type) { + case *gif.GIF: + match = s.resolutions.ClosestMatch(r.Resolution, m.Image[0].Bounds()) + case image.Image: + match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds()) + } + + thumbnail, err := r.Generator.GenerateThumbnail(match, img) + if err != nil { + return nil, err + } dst := new(bytes.Buffer) - err := r.Encoder.Encode(dst, thumbnail) + err = r.Encoder.Encode(dst, thumbnail) if err != nil { return nil, err } @@ -79,10 +91,6 @@ func (s SimpleManager) Get(r Request) ([]byte, bool) { return s.storage.Get(k) } -func (s SimpleManager) generate(r image.Rectangle, img image.Image) image.Image { - return imaging.Thumbnail(img, r.Dx(), r.Dy(), imaging.Lanczos) -} - func mapToStorageRequest(r Request) storage.Request { return storage.Request{ Checksum: r.Checksum, diff --git a/thumbnails/pkg/thumbnail/thumbnail_test.go b/thumbnails/pkg/thumbnail/thumbnail_test.go index 94bf0a27733..76be41a6688 100644 --- a/thumbnails/pkg/thumbnail/thumbnail_test.go +++ b/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -33,14 +33,14 @@ func BenchmarkGet(b *testing.B) { res, _ := ParseResolution("32x32") req := Request{ Resolution: res, - Checksum: "1872ade88f3013edeb33decd74a4f947", + Checksum: "1872ade88f3013edeb33decd74a4f947", } cwd, _ := os.Getwd() p := filepath.Join(cwd, "../../testdata/oc.png") f, _ := os.Open(p) defer f.Close() img, ext, _ := image.Decode(f) - req.Encoder = EncoderForType(ext) + req.Encoder, _ = EncoderForType(ext) for i := 0; i < b.N; i++ { _, _ = sut.Generate(req, img) } diff --git a/webdav/pkg/service/v0/service.go b/webdav/pkg/service/v0/service.go index 40eefeff01d..c1d92011d5a 100644 --- a/webdav/pkg/service/v0/service.go +++ b/webdav/pkg/service/v0/service.go @@ -286,7 +286,9 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { func extensionToThumbnailType(ext string) thumbnailssvc.GetThumbnailRequest_ThumbnailType { switch strings.ToUpper(ext) { - case "GIF", "PNG": + case "GIF": + return thumbnailssvc.GetThumbnailRequest_GIF + case "PNG": return thumbnailssvc.GetThumbnailRequest_PNG default: return thumbnailssvc.GetThumbnailRequest_JPG