Merge pull request #9011 from kobergj/BackportRevaBump

[full-ci] Backport reva bump
This commit is contained in:
kobergj
2024-04-30 09:23:38 +02:00
committed by GitHub
30 changed files with 4703 additions and 292 deletions

View File

@@ -0,0 +1,11 @@
Bugfix: Update reva to v2.19.5
We updated reva to v2.19.5
* Bugfix [cs3org/reva#4654](https://github.com/cs3org/reva/pull/4654): Write blob based on session id
* Bugfix [cs3org/reva#4666](https://github.com/cs3org/reva/pull/4666): Fix uploading via a public link
* Bugfix [cs3org/reva#4665](https://github.com/cs3org/reva/pull/4665): Fix creating documents in nested folders of public shares
* Enhancement [cs3org/reva#4655](https://github.com/cs3org/reva/pull/4655): Bump mockery to v2.40.2
* Enhancement [cs3org/reva#4664](https://github.com/cs3org/reva/pull/4664): Add ScanData to Uploadsession
https://github.com/owncloud/ocis/pull/9011

View File

@@ -0,0 +1,5 @@
Bugfix: Fix infected file handling
Reworks virus handling. Shows scandate and outcome on ocis storage-users uploads sessions. Avoids retrying infected files on ocis postprocessing restart.
https://github.com/owncloud/ocis/pull/9011

2
go.mod
View File

@@ -13,7 +13,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.9.0
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
github.com/cs3org/reva/v2 v2.19.5
github.com/cs3org/reva/v2 v2.19.6
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/egirna/icap-client v0.1.1

4
go.sum
View File

@@ -1019,8 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva/v2 v2.19.5 h1:Qh38wpPovnb0jPpgGR6L6HfbQ8vwObcrB8yUCRJldSw=
github.com/cs3org/reva/v2 v2.19.5/go.mod h1:GRUrOp5HbFVwZTgR9bVrMZ/MvVy+Jhxw1PdMmhhKP9E=
github.com/cs3org/reva/v2 v2.19.6 h1:n7fsTwdIN20vmQFkfOiJUbF2D7JFMLLMgOnEBRM5OTU=
github.com/cs3org/reva/v2 v2.19.6/go.mod h1:GRUrOp5HbFVwZTgR9bVrMZ/MvVy+Jhxw1PdMmhhKP9E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=

View File

@@ -146,7 +146,7 @@ func (av Antivirus) processEvent(e events.Event, s events.Publisher) error {
}
if av.c.DebugScanOutcome != "" {
av.l.Warn().Str("antivir, clamav", ">>>>>>> ANTIVIRUS_DEBUG_SCAN_OUTCOME IS SET NO ACTUAL VIRUS SCAN IS PERFORMED!")
av.l.Warn().Str("antivir, clamav", ">>>>>>> ANTIVIRUS_DEBUG_SCAN_OUTCOME IS SET NO ACTUAL VIRUS SCAN IS PERFORMED!").Send()
if err := events.Publish(ctx, s, events.PostprocessingStepFinished{
FinishedStep: events.PPStepAntivirus,
Outcome: events.PostprocessingOutcome(av.c.DebugScanOutcome),
@@ -158,7 +158,6 @@ func (av Antivirus) processEvent(e events.Event, s events.Publisher) error {
Description: "DEBUG: forced outcome",
Scandate: time.Now(),
ResourceID: ev.ResourceID,
ErrorMsg: "DEBUG: forced outcome",
},
}); err != nil {
av.l.Fatal().Err(err).Str("uploadid", ev.UploadID).Interface("resourceID", ev.ResourceID).Msg("cannot publish events - exiting")

View File

@@ -97,6 +97,11 @@ func (cl *ClientlogService) processEvent(event events.Event) {
default:
err = errors.New("unhandled event")
case events.UploadReady:
if e.Failed {
// we don't inform about failed uploads yet
return
}
info, err := utils.GetResource(ctx, e.FileRef, gwc)
if err != nil {
cl.log.Error().Err(err).Interface("event", event).Msg("error getting resource")

View File

@@ -21,6 +21,7 @@ type Postprocessing struct {
Steps []events.Postprocessingstep
Status Status
Failures int
Finished bool
config config.Postprocessing
}

View File

@@ -148,8 +148,14 @@ func (pps *PostprocessingService) processEvent(e events.Event) error {
next = pp.Delay()
case events.UploadReady:
if ev.Failed {
// the upload failed - let's keep it around for a while
return nil
// the upload failed - let's keep it around for a while - but mark it as finished
pp, err = pps.getPP(pps.store, ev.UploadID)
if err != nil {
pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload")
return fmt.Errorf("%w: cannot get upload", ErrEvent)
}
pp.Finished = true
return storePP(pps.store, pp)
}
// the storage provider thinks the upload is done - so no need to keep it any more
@@ -256,6 +262,11 @@ func (pps *PostprocessingService) resumePP(ctx context.Context, uploadID string)
return fmt.Errorf("cannot get upload: %w", err)
}
if pp.Finished {
// dont retry finished uploads
return nil
}
return events.Publish(ctx, pps.pub, pp.CurrentStep())
}

View File

@@ -191,6 +191,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command {
if c.Bool("json") {
for _, u := range uploads {
ref := u.Reference()
sr, sd := u.ScanData()
s := struct {
ID string `json:"id"`
Space string `json:"space"`
@@ -201,6 +202,8 @@ func ListUploadSessions(cfg *config.Config) *cli.Command {
SpaceOwner *userpb.UserId `json:"spaceowner,omitempty"`
Expires time.Time `json:"expires"`
Processing bool `json:"processing"`
ScanDate time.Time `json:"virus_scan_date"`
ScanResult string `json:"virus_scan_result"`
}{
Space: ref.GetResourceId().GetSpaceId(),
ID: u.ID(),
@@ -211,6 +214,8 @@ func ListUploadSessions(cfg *config.Config) *cli.Command {
SpaceOwner: u.SpaceOwner(),
Expires: u.Expires(),
Processing: u.IsProcessing(),
ScanDate: sd,
ScanResult: sr,
}
j, err := json.Marshal(s)
if err != nil {
@@ -236,10 +241,11 @@ func ListUploadSessions(cfg *config.Config) *cli.Command {
// start a table
table = tw.NewWriter(os.Stdout)
table.SetHeader([]string{"Space", "Upload Id", "Name", "Offset", "Size", "Executant", "Owner", "Expires", "Processing"})
table.SetHeader([]string{"Space", "Upload Id", "Name", "Offset", "Size", "Executant", "Owner", "Expires", "Processing", "Scan Date", "Scan Result"})
table.SetAutoFormatHeaders(false)
for _, u := range uploads {
sr, sd := u.ScanData()
table.Append([]string{
u.Reference().ResourceId.GetSpaceId(),
u.ID(),
@@ -250,6 +256,8 @@ func ListUploadSessions(cfg *config.Config) *cli.Command {
u.SpaceOwner().GetOpaqueId(),
u.Expires().Format(time.RFC3339),
strconv.FormatBool(u.IsProcessing()),
sd.Format(time.RFC3339),
sr,
})
if c.Bool("restart") {

View File

@@ -261,7 +261,7 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if err != nil {
return false, err
}
if statResponse.Status.Code != rpc.Code_CODE_OK {
if statResponse.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
}
@@ -313,14 +313,22 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if err != nil {
return false, err
}
if childStat.Status.Code != rpc.Code_CODE_OK {
if childStat.GetStatus().GetCode() == rpc.Code_CODE_NOT_FOUND && ref.GetPath() != "" && ref.GetPath() != "." {
// The resource does not seem to exist (yet?). We might be part of an initiate upload request.
// Stat the parent to get its path and check that against the root path.
childStat, err = client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: ref.GetResourceId()}})
if err != nil {
return false, err
}
}
if childStat.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
}
pathResp, err = client.GetPath(ctx, &provider.GetPathRequest{ResourceId: childStat.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
if pathResp.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
childPath = pathResp.Path

View File

@@ -319,9 +319,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.
w.Header().Set(net.HeaderOCMtime, httpRes.Header.Get(net.HeaderOCMtime))
}
if resid, err := storagespace.ParseID(httpRes.Header.Get(net.HeaderOCFileID)); err == nil {
sReq.Ref = &provider.Reference{
ResourceId: &resid,
if strings.HasPrefix(uReq.GetRef().GetPath(), "/public") && uReq.GetRef().GetResourceId() == nil {
// Use the path based request for the public link
sReq.Ref.Path = uReq.Ref.GetPath()
sReq.Ref.ResourceId = nil
} else {
if resid, err := storagespace.ParseID(httpRes.Header.Get(net.HeaderOCFileID)); err == nil {
sReq.Ref = &provider.Reference{
ResourceId: &resid,
}
}
}
finishUpload = httpRes.Header.Get(net.HeaderUploadOffset) == r.Header.Get(net.HeaderUploadLength)

View File

@@ -33,8 +33,6 @@ import (
"github.com/jellydator/ttlcache/v2"
)
//go:generate make --no-print-directory -C ../../../.. mockery NAME=UserConverter
// DBShare stores information about user and public shares.
type DBShare struct {
ID string

View File

@@ -37,8 +37,6 @@ import (
"github.com/jellydator/ttlcache/v2"
)
//go:generate make --no-print-directory -C ../../../.. mockery NAME=UserConverter
// DBShare stores information about user and public shares.
type DBShare struct {
ID string

View File

@@ -36,8 +36,6 @@ const (
NoState collaboration.ShareState = -1
)
//go:generate make --no-print-directory -C ../.. mockery NAME=Manager
// Metadata contains Metadata for a share
type Metadata struct {
ETag string

View File

@@ -54,8 +54,6 @@ func init() {
tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree")
}
//go:generate make --no-print-directory -C ../../../../.. mockery NAME=Blobstore
// Blobstore defines an interface for storing blobs in a blobstore
type Blobstore interface {
Upload(node *node.Node, source string) error

View File

@@ -48,8 +48,6 @@ import (
"google.golang.org/grpc"
)
//go:generate make --no-print-directory -C ../../../.. mockery NAME=StorageProviderClient
func init() {
pkgregistry.Register("spaces", NewDefault)
}

View File

@@ -76,6 +76,9 @@ type UploadSession interface {
// Purge allows completely removing an upload. Should emit a PostprocessingFinished event with a Delete outcome
Purge(ctx context.Context) error
// ScanData returns the scan data for the UploadSession
ScanData() (string, time.Time)
}
// UploadSessionFilter can be used to filter upload sessions

View File

@@ -310,9 +310,16 @@ func (fs *Decomposedfs) Postprocessing(ch <-chan events.Event) {
now := time.Now()
if failed {
// propagate sizeDiff after failed postprocessing
if err := fs.tp.Propagate(ctx, n, -session.SizeDiff()); err != nil {
log.Error().Err(err).Str("uploadID", ev.UploadID).Msg("could not propagate tree size change")
// if no other upload session is in progress (processing id != session id) or has finished (processing id == "")
latestSession, err := n.ProcessingID(ctx)
if err != nil {
log.Error().Err(err).Str("node", n.ID).Str("uploadID", ev.UploadID).Msg("reading node for session failed")
}
if latestSession == session.ID() {
// propagate reverted sizeDiff after failed postprocessing
if err := fs.tp.Propagate(ctx, n, -session.SizeDiff()); err != nil {
log.Error().Err(err).Str("uploadID", ev.UploadID).Msg("could not propagate tree size change")
}
}
} else if p := getParent(); p != nil {
// update parent tmtime to propagate etag change after successful postprocessing
@@ -486,6 +493,12 @@ func (fs *Decomposedfs) Postprocessing(ch <-chan events.Event) {
log.Error().Err(err).Interface("uploadID", ev.UploadID).Msg("Failed to get node after scan")
continue
}
sublog := log.With().Str("spaceid", session.SpaceID()).Str("nodeid", session.NodeID()).Logger()
session.SetScanData(res.Description, res.Scandate)
if err := session.Persist(ctx); err != nil {
sublog.Error().Err(err).Msg("Failed to persist scan results")
}
}
if err := n.SetScanData(ctx, res.Description, res.Scandate); err != nil {

View File

@@ -54,8 +54,6 @@ import (
"go.opentelemetry.io/otel/trace"
)
//go:generate make --no-print-directory -C ../../../../.. mockery NAME=Tree
var tracer trace.Tracer
func init() {
@@ -1278,6 +1276,12 @@ func (n *Node) IsProcessing(ctx context.Context) bool {
return err == nil && strings.HasPrefix(v, ProcessingStatus)
}
// ProcessingID returns the latest upload session id
func (n *Node) ProcessingID(ctx context.Context) (string, error) {
v, err := n.XattrString(ctx, prefixes.StatusPrefix)
return strings.TrimPrefix(v, ProcessingStatus), err
}
// IsSpaceRoot checks if the node is a space root
func (n *Node) IsSpaceRoot(ctx context.Context) bool {
_, err := n.Xattr(ctx, prefixes.SpaceNameAttr)

View File

@@ -16,9 +16,6 @@ import (
"google.golang.org/grpc"
)
//go:generate make --no-print-directory -C ../../../../.. mockery NAME=PermissionsChecker
//go:generate make --no-print-directory -C ../../../../.. mockery NAME=CS3PermissionsClient
var (
tracer trace.Tracer
)

View File

@@ -53,8 +53,6 @@ func init() {
tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree")
}
//go:generate make --no-print-directory -C ../../../../.. mockery NAME=Blobstore
// Blobstore defines an interface for storing blobs in a blobstore
type Blobstore interface {
Upload(node *node.Node, source string) error

View File

@@ -297,7 +297,8 @@ func (s *OcisSession) MTime() time.Time {
// IsProcessing returns true if all bytes have been received. The session then has entered postprocessing state.
func (s *OcisSession) IsProcessing() bool {
return s.info.Size == s.info.Offset
// We might need a more sophisticated way to determine processing status soon
return s.info.Size == s.info.Offset && s.info.MetaData["scanResult"] == ""
}
// binPath returns the path to the file storing the binary data.
@@ -305,6 +306,27 @@ func (s *OcisSession) binPath() string {
return filepath.Join(s.store.root, "uploads", s.info.ID)
}
// InitiatorID returns the id of the initiating client
func (s *OcisSession) InitiatorID() string {
return s.info.MetaData["initiatorid"]
}
// SetScanData sets virus scan data to the upload session
func (s *OcisSession) SetScanData(result string, date time.Time) {
s.info.MetaData["scanResult"] = result
s.info.MetaData["scanDate"] = date.Format(time.RFC3339)
}
// ScanData returns the virus scan data
func (s *OcisSession) ScanData() (string, time.Time) {
date := s.info.MetaData["scanDate"]
if date == "" {
return "", time.Time{}
}
d, _ := time.Parse(time.RFC3339, date)
return s.info.MetaData["scanResult"], d
}
// sessionPath returns the path to the .info file storing the file's info.
func sessionPath(root, id string) string {
return filepath.Join(root, "uploads", id+".info")

View File

@@ -205,7 +205,15 @@ func (store OcisStore) CreateNodeForUpload(session *OcisSession, initAttrs node.
}
var f *lockedfile.File
if session.NodeExists() {
if session.NodeExists() { // TODO this is wrong. The node should be created when the upload starts, the revisions should be created independently of the node
// we do not need to propagate a change when a node is created, only when the upload is ready.
// that still creates problems for desktop clients because if another change causes propagation it will detects an empty file
// so the first upload has to point to the first revision with the expected size. The file cannot be downloaded, but it can be overwritten (which will create a new revision and make the node reflect the latest revision)
// any finished postprocessing will not affect the node metadata.
// *thinking* but then initializing an upload will lock the file until the upload has finished. That sucks.
// so we have to check if the node has been created meanwhile (well, only in case the upload does not know the nodeid ... or the NodeExists array that is checked by session.NodeExists())
// FIXME look at the disk again to see if the file has been created in between, or just try initializing a new node and do the update existing node as a fallback. <- the latter!
f, err = store.updateExistingNode(ctx, session, n, session.SpaceID(), uint64(session.Size()))
if f != nil {
appctx.GetLogger(ctx).Info().Str("lockfile", f.Name()).Interface("err", err).Msg("got lock file from updateExistingNode")

View File

@@ -276,14 +276,12 @@ func (session *OcisSession) ConcatUploads(_ context.Context, uploads []tusd.Uplo
func (session *OcisSession) Finalize() (err error) {
ctx, span := tracer.Start(session.Context(context.Background()), "Finalize")
defer span.End()
n, err := session.Node(ctx)
if err != nil {
return err
}
revisionNode := &node.Node{SpaceID: session.SpaceID(), BlobID: session.ID(), Blobsize: session.Size()}
// upload the data to the blobstore
_, subspan := tracer.Start(ctx, "WriteBlob")
err = session.store.tp.WriteBlob(n, session.binPath())
err = session.store.tp.WriteBlob(revisionNode, session.binPath())
subspan.End()
if err != nil {
return errors.Wrap(err, "failed to upload file to blobstore")
@@ -316,12 +314,12 @@ func (session *OcisSession) Cleanup(revertNodeMetadata, cleanBin, cleanInfo bool
ctx := session.Context(context.Background())
if revertNodeMetadata {
n, err := session.Node(ctx)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Str("node", n.ID).Str("sessionid", session.ID()).Msg("reading node for session failed")
}
if session.NodeExists() {
p := session.info.MetaData["versionsPath"]
n, err := session.Node(ctx)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Str("sessionid", session.ID()).Msg("reading node for session failed")
}
if err := session.store.lu.CopyMetadata(ctx, p, n.InternalPath(), func(attributeName string, value []byte) (newValue []byte, copy bool) {
return value, strings.HasPrefix(attributeName, prefixes.ChecksumPrefix) ||
attributeName == prefixes.TypeAttr ||
@@ -337,7 +335,16 @@ func (session *OcisSession) Cleanup(revertNodeMetadata, cleanBin, cleanInfo bool
}
} else {
session.removeNode(ctx)
// if no other upload session is in progress (processing id != session id) or has finished (processing id == "")
latestSession, err := n.ProcessingID(ctx)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Str("node", n.ID).Str("sessionid", session.ID()).Msg("reading processingid for session failed")
}
if latestSession == session.ID() {
// actually delete the node
session.removeNode(ctx)
}
// FIXME else if the upload has become a revision, delete the revision, or if it is the last one, delete the node
}
}

View File

@@ -36,8 +36,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storage/utils/sync"
)
//go:generate make --no-print-directory -C ../../../.. mockery NAME=Indexer
// Indexer is a facade to configure and query over multiple indices.
type Indexer interface {
AddIndex(t interface{}, indexBy option.IndexBy, pkName, entityDirName, indexType string, bound *option.Bound, caseInsensitive bool) error

View File

@@ -28,8 +28,6 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
)
//go:generate make --no-print-directory -C ../../../.. mockery NAME=Storage
// UploadRequest represents an upload request and its options
type UploadRequest struct {
Path string

View File

@@ -16,7 +16,7 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
// Code generated by mockery v2.22.1. DO NOT EDIT.
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
@@ -35,6 +35,14 @@ type CollaborationAPIClient struct {
mock.Mock
}
type CollaborationAPIClient_Expecter struct {
mock *mock.Mock
}
func (_m *CollaborationAPIClient) EXPECT() *CollaborationAPIClient_Expecter {
return &CollaborationAPIClient_Expecter{mock: &_m.Mock}
}
// CreateShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) CreateShare(ctx context.Context, in *collaborationv1beta1.CreateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.CreateShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -46,6 +54,10 @@ func (_m *CollaborationAPIClient) CreateShare(ctx context.Context, in *collabora
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for CreateShare")
}
var r0 *collaborationv1beta1.CreateShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) (*collaborationv1beta1.CreateShareResponse, error)); ok {
@@ -68,6 +80,43 @@ func (_m *CollaborationAPIClient) CreateShare(ctx context.Context, in *collabora
return r0, r1
}
// CollaborationAPIClient_CreateShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateShare'
type CollaborationAPIClient_CreateShare_Call struct {
*mock.Call
}
// CreateShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.CreateShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) CreateShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_CreateShare_Call {
return &CollaborationAPIClient_CreateShare_Call{Call: _e.mock.On("CreateShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_CreateShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.CreateShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_CreateShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.CreateShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_CreateShare_Call) Return(_a0 *collaborationv1beta1.CreateShareResponse, _a1 error) *CollaborationAPIClient_CreateShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_CreateShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) (*collaborationv1beta1.CreateShareResponse, error)) *CollaborationAPIClient_CreateShare_Call {
_c.Call.Return(run)
return _c
}
// GetReceivedShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) GetReceivedShare(ctx context.Context, in *collaborationv1beta1.GetReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -79,6 +128,10 @@ func (_m *CollaborationAPIClient) GetReceivedShare(ctx context.Context, in *coll
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetReceivedShare")
}
var r0 *collaborationv1beta1.GetReceivedShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error)); ok {
@@ -101,6 +154,43 @@ func (_m *CollaborationAPIClient) GetReceivedShare(ctx context.Context, in *coll
return r0, r1
}
// CollaborationAPIClient_GetReceivedShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetReceivedShare'
type CollaborationAPIClient_GetReceivedShare_Call struct {
*mock.Call
}
// GetReceivedShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.GetReceivedShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) GetReceivedShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_GetReceivedShare_Call {
return &CollaborationAPIClient_GetReceivedShare_Call{Call: _e.mock.On("GetReceivedShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_GetReceivedShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.GetReceivedShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_GetReceivedShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.GetReceivedShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_GetReceivedShare_Call) Return(_a0 *collaborationv1beta1.GetReceivedShareResponse, _a1 error) *CollaborationAPIClient_GetReceivedShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_GetReceivedShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error)) *CollaborationAPIClient_GetReceivedShare_Call {
_c.Call.Return(run)
return _c
}
// GetShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) GetShare(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -112,6 +202,10 @@ func (_m *CollaborationAPIClient) GetShare(ctx context.Context, in *collaboratio
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetShare")
}
var r0 *collaborationv1beta1.GetShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error)); ok {
@@ -134,6 +228,43 @@ func (_m *CollaborationAPIClient) GetShare(ctx context.Context, in *collaboratio
return r0, r1
}
// CollaborationAPIClient_GetShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetShare'
type CollaborationAPIClient_GetShare_Call struct {
*mock.Call
}
// GetShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.GetShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) GetShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_GetShare_Call {
return &CollaborationAPIClient_GetShare_Call{Call: _e.mock.On("GetShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_GetShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_GetShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.GetShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_GetShare_Call) Return(_a0 *collaborationv1beta1.GetShareResponse, _a1 error) *CollaborationAPIClient_GetShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_GetShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error)) *CollaborationAPIClient_GetShare_Call {
_c.Call.Return(run)
return _c
}
// ListReceivedShares provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) ListReceivedShares(ctx context.Context, in *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) {
_va := make([]interface{}, len(opts))
@@ -145,6 +276,10 @@ func (_m *CollaborationAPIClient) ListReceivedShares(ctx context.Context, in *co
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListReceivedShares")
}
var r0 *collaborationv1beta1.ListReceivedSharesResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error)); ok {
@@ -167,6 +302,43 @@ func (_m *CollaborationAPIClient) ListReceivedShares(ctx context.Context, in *co
return r0, r1
}
// CollaborationAPIClient_ListReceivedShares_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReceivedShares'
type CollaborationAPIClient_ListReceivedShares_Call struct {
*mock.Call
}
// ListReceivedShares is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.ListReceivedSharesRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) ListReceivedShares(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_ListReceivedShares_Call {
return &CollaborationAPIClient_ListReceivedShares_Call{Call: _e.mock.On("ListReceivedShares",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_ListReceivedShares_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_ListReceivedShares_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.ListReceivedSharesRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_ListReceivedShares_Call) Return(_a0 *collaborationv1beta1.ListReceivedSharesResponse, _a1 error) *CollaborationAPIClient_ListReceivedShares_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_ListReceivedShares_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error)) *CollaborationAPIClient_ListReceivedShares_Call {
_c.Call.Return(run)
return _c
}
// ListShares provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) ListShares(ctx context.Context, in *collaborationv1beta1.ListSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListSharesResponse, error) {
_va := make([]interface{}, len(opts))
@@ -178,6 +350,10 @@ func (_m *CollaborationAPIClient) ListShares(ctx context.Context, in *collaborat
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListShares")
}
var r0 *collaborationv1beta1.ListSharesResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) (*collaborationv1beta1.ListSharesResponse, error)); ok {
@@ -200,6 +376,43 @@ func (_m *CollaborationAPIClient) ListShares(ctx context.Context, in *collaborat
return r0, r1
}
// CollaborationAPIClient_ListShares_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListShares'
type CollaborationAPIClient_ListShares_Call struct {
*mock.Call
}
// ListShares is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.ListSharesRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) ListShares(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_ListShares_Call {
return &CollaborationAPIClient_ListShares_Call{Call: _e.mock.On("ListShares",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_ListShares_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.ListSharesRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_ListShares_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.ListSharesRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_ListShares_Call) Return(_a0 *collaborationv1beta1.ListSharesResponse, _a1 error) *CollaborationAPIClient_ListShares_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_ListShares_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) (*collaborationv1beta1.ListSharesResponse, error)) *CollaborationAPIClient_ListShares_Call {
_c.Call.Return(run)
return _c
}
// RemoveShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) RemoveShare(ctx context.Context, in *collaborationv1beta1.RemoveShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.RemoveShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -211,6 +424,10 @@ func (_m *CollaborationAPIClient) RemoveShare(ctx context.Context, in *collabora
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for RemoveShare")
}
var r0 *collaborationv1beta1.RemoveShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) (*collaborationv1beta1.RemoveShareResponse, error)); ok {
@@ -233,6 +450,43 @@ func (_m *CollaborationAPIClient) RemoveShare(ctx context.Context, in *collabora
return r0, r1
}
// CollaborationAPIClient_RemoveShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveShare'
type CollaborationAPIClient_RemoveShare_Call struct {
*mock.Call
}
// RemoveShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.RemoveShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) RemoveShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_RemoveShare_Call {
return &CollaborationAPIClient_RemoveShare_Call{Call: _e.mock.On("RemoveShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_RemoveShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.RemoveShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_RemoveShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.RemoveShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_RemoveShare_Call) Return(_a0 *collaborationv1beta1.RemoveShareResponse, _a1 error) *CollaborationAPIClient_RemoveShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_RemoveShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) (*collaborationv1beta1.RemoveShareResponse, error)) *CollaborationAPIClient_RemoveShare_Call {
_c.Call.Return(run)
return _c
}
// UpdateReceivedShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) UpdateReceivedShare(ctx context.Context, in *collaborationv1beta1.UpdateReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -244,6 +498,10 @@ func (_m *CollaborationAPIClient) UpdateReceivedShare(ctx context.Context, in *c
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for UpdateReceivedShare")
}
var r0 *collaborationv1beta1.UpdateReceivedShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error)); ok {
@@ -266,6 +524,43 @@ func (_m *CollaborationAPIClient) UpdateReceivedShare(ctx context.Context, in *c
return r0, r1
}
// CollaborationAPIClient_UpdateReceivedShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReceivedShare'
type CollaborationAPIClient_UpdateReceivedShare_Call struct {
*mock.Call
}
// UpdateReceivedShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.UpdateReceivedShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) UpdateReceivedShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_UpdateReceivedShare_Call {
return &CollaborationAPIClient_UpdateReceivedShare_Call{Call: _e.mock.On("UpdateReceivedShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_UpdateReceivedShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.UpdateReceivedShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_UpdateReceivedShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.UpdateReceivedShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_UpdateReceivedShare_Call) Return(_a0 *collaborationv1beta1.UpdateReceivedShareResponse, _a1 error) *CollaborationAPIClient_UpdateReceivedShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_UpdateReceivedShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error)) *CollaborationAPIClient_UpdateReceivedShare_Call {
_c.Call.Return(run)
return _c
}
// UpdateShare provides a mock function with given fields: ctx, in, opts
func (_m *CollaborationAPIClient) UpdateShare(ctx context.Context, in *collaborationv1beta1.UpdateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateShareResponse, error) {
_va := make([]interface{}, len(opts))
@@ -277,6 +572,10 @@ func (_m *CollaborationAPIClient) UpdateShare(ctx context.Context, in *collabora
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for UpdateShare")
}
var r0 *collaborationv1beta1.UpdateShareResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) (*collaborationv1beta1.UpdateShareResponse, error)); ok {
@@ -299,13 +598,49 @@ func (_m *CollaborationAPIClient) UpdateShare(ctx context.Context, in *collabora
return r0, r1
}
type mockConstructorTestingTNewCollaborationAPIClient interface {
mock.TestingT
Cleanup(func())
// CollaborationAPIClient_UpdateShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateShare'
type CollaborationAPIClient_UpdateShare_Call struct {
*mock.Call
}
// UpdateShare is a helper method to define mock.On call
// - ctx context.Context
// - in *collaborationv1beta1.UpdateShareRequest
// - opts ...grpc.CallOption
func (_e *CollaborationAPIClient_Expecter) UpdateShare(ctx interface{}, in interface{}, opts ...interface{}) *CollaborationAPIClient_UpdateShare_Call {
return &CollaborationAPIClient_UpdateShare_Call{Call: _e.mock.On("UpdateShare",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *CollaborationAPIClient_UpdateShare_Call) Run(run func(ctx context.Context, in *collaborationv1beta1.UpdateShareRequest, opts ...grpc.CallOption)) *CollaborationAPIClient_UpdateShare_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*collaborationv1beta1.UpdateShareRequest), variadicArgs...)
})
return _c
}
func (_c *CollaborationAPIClient_UpdateShare_Call) Return(_a0 *collaborationv1beta1.UpdateShareResponse, _a1 error) *CollaborationAPIClient_UpdateShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *CollaborationAPIClient_UpdateShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) (*collaborationv1beta1.UpdateShareResponse, error)) *CollaborationAPIClient_UpdateShare_Call {
_c.Call.Return(run)
return _c
}
// NewCollaborationAPIClient creates a new instance of CollaborationAPIClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewCollaborationAPIClient(t mockConstructorTestingTNewCollaborationAPIClient) *CollaborationAPIClient {
// The first argument is typically a *testing.T value.
func NewCollaborationAPIClient(t interface {
mock.TestingT
Cleanup(func())
}) *CollaborationAPIClient {
mock := &CollaborationAPIClient{}
mock.Mock.Test(t)

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,231 +1,231 @@
# Imaging
Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
All the image processing functions provided by the package accept any image type that implements `image.Image` interface
as an input, and return a new image of `*image.NRGBA` type (32bit RGBA colors, non-premultiplied alpha).
## Installation
go get -u github.com/kovidgoyal/imaging
## Documentation
https://pkg.go.dev/github.com/kovidgoyal/imaging
## Usage examples
A few usage examples can be found below. See the documentation for the full list of supported functions.
### Image resizing
```go
// Resize srcImage to size = 128x128px using the Lanczos filter.
dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
// Resize srcImage to width = 800px preserving the aspect ratio.
dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
// Scale down srcImage to fit the 800x600px bounding box.
dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
// Resize and crop the srcImage to fill the 100x100px area.
dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
```
Imaging supports image resizing using various resampling filters. The most notable ones:
- `Lanczos` - A high-quality resampling filter for photographic images yielding sharp results.
- `CatmullRom` - A sharp cubic filter that is faster than Lanczos filter while providing similar results.
- `MitchellNetravali` - A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
- `Linear` - Bilinear resampling filter, produces 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.
The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
**Resampling filters comparison**
Original image:
![srcImage](testdata/branches.png)
The same image resized from 600x400px to 150x100px using different resampling filters.
From faster (lower quality) to slower (higher quality):
Filter | Resize result
--------------------------|---------------------------------------------
`imaging.NearestNeighbor` | ![dstImage](testdata/out_resize_nearest.png)
`imaging.Linear` | ![dstImage](testdata/out_resize_linear.png)
`imaging.CatmullRom` | ![dstImage](testdata/out_resize_catrom.png)
`imaging.Lanczos` | ![dstImage](testdata/out_resize_lanczos.png)
### Gaussian Blur
```go
dstImage := imaging.Blur(srcImage, 0.5)
```
Sigma parameter allows to control the strength of the blurring effect.
Original image | Sigma = 0.5 | Sigma = 1.5
-----------------------------------|----------------------------------------|---------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_blur_0.5.png) | ![dstImage](testdata/out_blur_1.5.png)
### Sharpening
```go
dstImage := imaging.Sharpen(srcImage, 0.5)
```
`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
Original image | Sigma = 0.5 | Sigma = 1.5
-----------------------------------|-------------------------------------------|------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_sharpen_0.5.png) | ![dstImage](testdata/out_sharpen_1.5.png)
### Gamma correction
```go
dstImage := imaging.AdjustGamma(srcImage, 0.75)
```
Original image | Gamma = 0.75 | Gamma = 1.25
-----------------------------------|------------------------------------------|-----------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_gamma_0.75.png) | ![dstImage](testdata/out_gamma_1.25.png)
### Contrast adjustment
```go
dstImage := imaging.AdjustContrast(srcImage, 20)
```
Original image | Contrast = 15 | Contrast = -15
-----------------------------------|--------------------------------------------|-------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_contrast_p15.png) | ![dstImage](testdata/out_contrast_m15.png)
### Brightness adjustment
```go
dstImage := imaging.AdjustBrightness(srcImage, 20)
```
Original image | Brightness = 10 | Brightness = -10
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_brightness_p10.png) | ![dstImage](testdata/out_brightness_m10.png)
### Saturation adjustment
```go
dstImage := imaging.AdjustSaturation(srcImage, 20)
```
Original image | Saturation = 30 | Saturation = -30
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_saturation_p30.png) | ![dstImage](testdata/out_saturation_m30.png)
### Hue adjustment
```go
dstImage := imaging.AdjustHue(srcImage, 20)
```
Original image | Hue = 60 | Hue = -60
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_hue_p60.png) | ![dstImage](testdata/out_hue_m60.png)
## FAQ
### Incorrect image orientation after processing (e.g. an image appears rotated after resizing)
Most probably, the given image contains the EXIF orientation tag.
The standard `image/*` packages do not support loading and saving
this kind of information. To fix the issue, try opening images with
the `AutoOrientation` decode option. If this option is set to `true`,
the image orientation is changed after decoding, according to the
orientation tag (if present). Here's the example:
```go
img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
```
### What's the difference between `imaging` and `gift` packages?
[imaging](https://github.com/kovidgoyal/imaging)
is designed to be a lightweight and simple image manipulation package.
It provides basic image processing functions and a few helper functions
such as `Open` and `Save`. It consistently returns *image.NRGBA image
type (8 bits per channel, RGBA).
[gift](https://github.com/disintegration/gift)
supports more advanced image processing, for example, sRGB/Linear color
space conversions. It also supports different output image types
(e.g. 16 bits per channel) and provides easy-to-use API for chaining
multiple processing steps together.
## Example code
```go
package main
import (
"image"
"image/color"
"log"
"github.com/kovidgoyal/imaging"
)
func main() {
// Open a test image.
src, err := imaging.Open("testdata/flowers.png")
if err != nil {
log.Fatalf("failed to open image: %v", err)
}
// Crop the original image to 300x300px size using the center anchor.
src = imaging.CropAnchor(src, 300, 300, imaging.Center)
// Resize the cropped image to width = 200px preserving the aspect ratio.
src = imaging.Resize(src, 200, 0, imaging.Lanczos)
// Create a blurred version of the image.
img1 := imaging.Blur(src, 5)
// Create a grayscale version of the image with higher contrast and sharpness.
img2 := imaging.Grayscale(src)
img2 = imaging.AdjustContrast(img2, 20)
img2 = imaging.Sharpen(img2, 2)
// Create an inverted version of the image.
img3 := imaging.Invert(src)
// Create an embossed version of the image using a convolution filter.
img4 := imaging.Convolve3x3(
src,
[9]float64{
-1, -1, 0,
-1, 1, 1,
0, 1, 1,
},
nil,
)
// Create a new image and paste the four produced images into it.
dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0})
dst = imaging.Paste(dst, img1, image.Pt(0, 0))
dst = imaging.Paste(dst, img2, image.Pt(0, 200))
dst = imaging.Paste(dst, img3, image.Pt(200, 0))
dst = imaging.Paste(dst, img4, image.Pt(200, 200))
// Save the resulting image as JPEG.
err = imaging.Save(dst, "testdata/out_example.jpg")
if err != nil {
log.Fatalf("failed to save image: %v", err)
}
}
```
Output:
![dstImage](testdata/out_example.jpg)
# Imaging
Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
All the image processing functions provided by the package accept any image type that implements `image.Image` interface
as an input, and return a new image of `*image.NRGBA` type (32bit RGBA colors, non-premultiplied alpha).
## Installation
go get -u github.com/kovidgoyal/imaging
## Documentation
https://pkg.go.dev/github.com/kovidgoyal/imaging
## Usage examples
A few usage examples can be found below. See the documentation for the full list of supported functions.
### Image resizing
```go
// Resize srcImage to size = 128x128px using the Lanczos filter.
dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
// Resize srcImage to width = 800px preserving the aspect ratio.
dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
// Scale down srcImage to fit the 800x600px bounding box.
dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
// Resize and crop the srcImage to fill the 100x100px area.
dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
```
Imaging supports image resizing using various resampling filters. The most notable ones:
- `Lanczos` - A high-quality resampling filter for photographic images yielding sharp results.
- `CatmullRom` - A sharp cubic filter that is faster than Lanczos filter while providing similar results.
- `MitchellNetravali` - A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
- `Linear` - Bilinear resampling filter, produces 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.
The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
**Resampling filters comparison**
Original image:
![srcImage](testdata/branches.png)
The same image resized from 600x400px to 150x100px using different resampling filters.
From faster (lower quality) to slower (higher quality):
Filter | Resize result
--------------------------|---------------------------------------------
`imaging.NearestNeighbor` | ![dstImage](testdata/out_resize_nearest.png)
`imaging.Linear` | ![dstImage](testdata/out_resize_linear.png)
`imaging.CatmullRom` | ![dstImage](testdata/out_resize_catrom.png)
`imaging.Lanczos` | ![dstImage](testdata/out_resize_lanczos.png)
### Gaussian Blur
```go
dstImage := imaging.Blur(srcImage, 0.5)
```
Sigma parameter allows to control the strength of the blurring effect.
Original image | Sigma = 0.5 | Sigma = 1.5
-----------------------------------|----------------------------------------|---------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_blur_0.5.png) | ![dstImage](testdata/out_blur_1.5.png)
### Sharpening
```go
dstImage := imaging.Sharpen(srcImage, 0.5)
```
`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
Original image | Sigma = 0.5 | Sigma = 1.5
-----------------------------------|-------------------------------------------|------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_sharpen_0.5.png) | ![dstImage](testdata/out_sharpen_1.5.png)
### Gamma correction
```go
dstImage := imaging.AdjustGamma(srcImage, 0.75)
```
Original image | Gamma = 0.75 | Gamma = 1.25
-----------------------------------|------------------------------------------|-----------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_gamma_0.75.png) | ![dstImage](testdata/out_gamma_1.25.png)
### Contrast adjustment
```go
dstImage := imaging.AdjustContrast(srcImage, 20)
```
Original image | Contrast = 15 | Contrast = -15
-----------------------------------|--------------------------------------------|-------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_contrast_p15.png) | ![dstImage](testdata/out_contrast_m15.png)
### Brightness adjustment
```go
dstImage := imaging.AdjustBrightness(srcImage, 20)
```
Original image | Brightness = 10 | Brightness = -10
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_brightness_p10.png) | ![dstImage](testdata/out_brightness_m10.png)
### Saturation adjustment
```go
dstImage := imaging.AdjustSaturation(srcImage, 20)
```
Original image | Saturation = 30 | Saturation = -30
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_saturation_p30.png) | ![dstImage](testdata/out_saturation_m30.png)
### Hue adjustment
```go
dstImage := imaging.AdjustHue(srcImage, 20)
```
Original image | Hue = 60 | Hue = -60
-----------------------------------|----------------------------------------------|---------------------------------------------
![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_hue_p60.png) | ![dstImage](testdata/out_hue_m60.png)
## FAQ
### Incorrect image orientation after processing (e.g. an image appears rotated after resizing)
Most probably, the given image contains the EXIF orientation tag.
The standard `image/*` packages do not support loading and saving
this kind of information. To fix the issue, try opening images with
the `AutoOrientation` decode option. If this option is set to `true`,
the image orientation is changed after decoding, according to the
orientation tag (if present). Here's the example:
```go
img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
```
### What's the difference between `imaging` and `gift` packages?
[imaging](https://github.com/kovidgoyal/imaging)
is designed to be a lightweight and simple image manipulation package.
It provides basic image processing functions and a few helper functions
such as `Open` and `Save`. It consistently returns *image.NRGBA image
type (8 bits per channel, RGBA).
[gift](https://github.com/disintegration/gift)
supports more advanced image processing, for example, sRGB/Linear color
space conversions. It also supports different output image types
(e.g. 16 bits per channel) and provides easy-to-use API for chaining
multiple processing steps together.
## Example code
```go
package main
import (
"image"
"image/color"
"log"
"github.com/kovidgoyal/imaging"
)
func main() {
// Open a test image.
src, err := imaging.Open("testdata/flowers.png")
if err != nil {
log.Fatalf("failed to open image: %v", err)
}
// Crop the original image to 300x300px size using the center anchor.
src = imaging.CropAnchor(src, 300, 300, imaging.Center)
// Resize the cropped image to width = 200px preserving the aspect ratio.
src = imaging.Resize(src, 200, 0, imaging.Lanczos)
// Create a blurred version of the image.
img1 := imaging.Blur(src, 5)
// Create a grayscale version of the image with higher contrast and sharpness.
img2 := imaging.Grayscale(src)
img2 = imaging.AdjustContrast(img2, 20)
img2 = imaging.Sharpen(img2, 2)
// Create an inverted version of the image.
img3 := imaging.Invert(src)
// Create an embossed version of the image using a convolution filter.
img4 := imaging.Convolve3x3(
src,
[9]float64{
-1, -1, 0,
-1, 1, 1,
0, 1, 1,
},
nil,
)
// Create a new image and paste the four produced images into it.
dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0})
dst = imaging.Paste(dst, img1, image.Pt(0, 0))
dst = imaging.Paste(dst, img2, image.Pt(0, 200))
dst = imaging.Paste(dst, img3, image.Pt(200, 0))
dst = imaging.Paste(dst, img4, image.Pt(200, 200))
// Save the resulting image as JPEG.
err = imaging.Save(dst, "testdata/out_example.jpg")
if err != nil {
log.Fatalf("failed to save image: %v", err)
}
}
```
Output:
![dstImage](testdata/out_example.jpg)

2
vendor/modules.txt vendored
View File

@@ -359,7 +359,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
# github.com/cs3org/reva/v2 v2.19.5
# github.com/cs3org/reva/v2 v2.19.6
## explicit; go 1.21
github.com/cs3org/reva/v2/cmd/revad/internal/grace
github.com/cs3org/reva/v2/cmd/revad/runtime