From b1ddb5e2cc78ebebc571f3ba8933cc0300eb5856 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Wed, 5 Aug 2020 16:14:36 +0800 Subject: [PATCH] Implement the icon API to get the icon of artifact Implement the icon API to get the icon of artifact Signed-off-by: Wenkai Yin --- api/v2.0/swagger.yaml | 39 ++ icons/chart.png | Bin 0 -> 3792 bytes icons/cnab.png | Bin 0 -> 1455 bytes icons/default.png | Bin 0 -> 379 bytes icons/image.png | Bin 0 -> 1329 bytes .../postgresql/0040_2.1.0_schema.up.sql | 17 +- make/photon/core/Dockerfile | 1 + .../artifact/processor/chart/chart.go | 9 + .../artifact/processor/chart/chart_test.go | 69 +- .../artifact/processor/cnab/cnab.go | 2 + .../artifact/processor/cnab/cnab_test.go | 2 + src/controller/artifact/processor/default.go | 11 +- .../artifact/processor/default_test.go | 3 +- .../artifact/processor/image/index.go | 10 + .../artifact/processor/image/index_test.go | 12 +- .../artifact/processor/image/manifest_v1.go | 2 + .../processor/image/manifest_v1_test.go | 5 +- .../artifact/processor/image/manifest_v2.go | 10 + .../processor/image/manifest_v2_test.go | 27 +- src/controller/icon/controller.go | 144 ++++ src/controller/icon/controller_test.go | 82 +++ src/go.mod | 1 + src/go.sum | 2 + src/pkg/artifact/dao/model.go | 2 +- src/pkg/artifact/model.go | 4 +- src/server/v2.0/handler/handler.go | 1 + src/server/v2.0/handler/icon.go | 47 ++ src/server/v2.0/handler/model/artifact.go | 1 + src/vendor/github.com/nfnt/resize/.travis.yml | 7 + src/vendor/github.com/nfnt/resize/LICENSE | 13 + src/vendor/github.com/nfnt/resize/README.md | 151 +++++ .../github.com/nfnt/resize/converter.go | 438 +++++++++++++ src/vendor/github.com/nfnt/resize/filters.go | 143 ++++ src/vendor/github.com/nfnt/resize/nearest.go | 318 +++++++++ src/vendor/github.com/nfnt/resize/resize.go | 620 ++++++++++++++++++ .../github.com/nfnt/resize/thumbnail.go | 55 ++ src/vendor/github.com/nfnt/resize/ycc.go | 387 +++++++++++ src/vendor/modules.txt | 3 + 38 files changed, 2587 insertions(+), 51 deletions(-) create mode 100644 icons/chart.png create mode 100644 icons/cnab.png create mode 100644 icons/default.png create mode 100644 icons/image.png create mode 100644 src/controller/icon/controller.go create mode 100644 src/controller/icon/controller_test.go create mode 100644 src/server/v2.0/handler/icon.go create mode 100644 src/vendor/github.com/nfnt/resize/.travis.yml create mode 100644 src/vendor/github.com/nfnt/resize/LICENSE create mode 100644 src/vendor/github.com/nfnt/resize/README.md create mode 100644 src/vendor/github.com/nfnt/resize/converter.go create mode 100644 src/vendor/github.com/nfnt/resize/filters.go create mode 100644 src/vendor/github.com/nfnt/resize/nearest.go create mode 100644 src/vendor/github.com/nfnt/resize/resize.go create mode 100644 src/vendor/github.com/nfnt/resize/thumbnail.go create mode 100644 src/vendor/github.com/nfnt/resize/ycc.go diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 060ffee4a..ea5e1a307 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1206,6 +1206,27 @@ paths: $ref: '#/responses/404' '500': $ref: '#/responses/500' + /icons/{digest}: + get: + summary: Get artifact icon + description: Get the artifact icon with the specified digest. As the original icon image is resized and encoded before returning, the parameter "digest" in the path doesn't match the hash of the returned content + tags: + - icon + operationId: getIcon + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/digest' + responses: + '200': + description: Success + schema: + $ref: '#/definitions/Icon' + '400': + $ref: '#/responses/400' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' parameters: query: name: q @@ -1244,6 +1265,12 @@ parameters: description: The reference of the artifact, can be digest or tag required: true type: string + digest: + name: digest + in: path + description: The digest of the resource + required: true + type: string tagName: name: tag_name in: path @@ -1451,6 +1478,9 @@ definitions: type: integer format: int64 description: The size of the artifact + icon: + type: string + description: The digest of the icon push_time: type: string format: date-time @@ -1919,3 +1949,12 @@ definitions: type: boolean default: type: boolean + Icon: + type: object + properties: + content-type: + type: string + description: The content type of the icon + content: + type: string + description: The base64 encoded content of the icon diff --git a/icons/chart.png b/icons/chart.png new file mode 100644 index 0000000000000000000000000000000000000000..5efba6302a0f8fc8ad1a504052bba6aa55b46f96 GIT binary patch literal 3792 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 literal 0 HcmV?d00001 diff --git a/make/migrations/postgresql/0040_2.1.0_schema.up.sql b/make/migrations/postgresql/0040_2.1.0_schema.up.sql index 8c3a3a6c0..068b52d45 100644 --- a/make/migrations/postgresql/0040_2.1.0_schema.up.sql +++ b/make/migrations/postgresql/0040_2.1.0_schema.up.sql @@ -115,8 +115,6 @@ ALTER TABLE schedule DROP COLUMN IF EXISTS status; UPDATE registry SET type = 'quay' WHERE type = 'quay-io'; -ALTER TABLE artifact ADD COLUMN icon varchar(255); - CREATE TABLE IF NOT EXISTS data_migrations ( version int ); @@ -128,3 +126,18 @@ INSERT INTO data_migrations (version) VALUES ( END ); ALTER TABLE schema_migrations DROP COLUMN IF EXISTS data_version; + +ALTER TABLE artifact ADD COLUMN IF NOT EXISTS icon varchar(255); + +UPDATE artifact +SET icon=( +CASE + WHEN type='IMAGE' THEN + 'sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06' + WHEN type='CHART' THEN + 'sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518' + WHEN type='CNAB' THEN + 'sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd' + ELSE + 'sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f' +END); diff --git a/make/photon/core/Dockerfile b/make/photon/core/Dockerfile index 780464390..33a728839 100644 --- a/make/photon/core/Dockerfile +++ b/make/photon/core/Dockerfile @@ -8,6 +8,7 @@ COPY ./make/photon/core/entrypoint.sh /harbor/ COPY ./make/photon/core/harbor_core /harbor/ COPY ./src/core/views /harbor/views COPY ./make/migrations /harbor/migrations +COPY ./icons /harbor/icons RUN chown -R harbor:harbor /etc/pki/tls/certs \ && chown harbor:harbor /harbor/entrypoint.sh && chmod u+x /harbor/entrypoint.sh \ diff --git a/src/controller/artifact/processor/chart/chart.go b/src/controller/artifact/processor/chart/chart.go index 4a5206a62..efbbc417e 100644 --- a/src/controller/artifact/processor/chart/chart.go +++ b/src/controller/artifact/processor/chart/chart.go @@ -21,6 +21,7 @@ import ( ps "github.com/goharbor/harbor/src/controller/artifact/processor" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" @@ -57,6 +58,14 @@ type processor struct { chartOperator chart.Operator } +func (p *processor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error { + if err := p.ManifestProcessor.AbstractMetadata(ctx, artifact, manifest); err != nil { + return err + } + artifact.Icon = icon.DigestOfIconChart + return nil +} + func (p *processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*ps.Addition, error) { if addition != AdditionTypeValues && addition != AdditionTypeReadme && addition != AdditionTypeDependencies { return nil, errors.New(nil).WithCode(errors.BadRequestCode). diff --git a/src/controller/artifact/processor/chart/chart_test.go b/src/controller/artifact/processor/chart/chart_test.go index 682303521..759289b1f 100644 --- a/src/controller/artifact/processor/chart/chart_test.go +++ b/src/controller/artifact/processor/chart/chart_test.go @@ -15,11 +15,14 @@ package chart import ( + "bytes" "github.com/docker/distribution" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/artifact" chartserver "github.com/goharbor/harbor/src/pkg/chart" + "github.com/goharbor/harbor/src/testing/mock" "github.com/goharbor/harbor/src/testing/pkg/chart" "github.com/goharbor/harbor/src/testing/pkg/registry" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -30,6 +33,33 @@ import ( "testing" ) +var ( + chartManifest = `{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:76a59ebef39013bf7b57e411629b569a5175590024f31eeaaa577a0f8da9e523","size":528},"layers":[{"mediaType":"application/tar+gzip","digest":"sha256:0bd64cfb958b68c71b46597e22185a41e784dc96e04090bc7d2a480b704c3b65","size":12607}]}` + chartYaml = `{ + "name":"redis", + "home": "http://redis.io/", + "sources": [ + "https://github.com/bitnami/bitnami-docker-redis" + ], + "version": "3.2.5", + "description": "Open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.", + "keywords": [ + "redis", + "keyvalue", + "database" + ], + "maintainers": [ + { + "name": "bitnami-bot", + "email":"containers@bitnami.com" + } + ], + "icon": "https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png", + "apiVersion": "v1", + "appVersion": "4.0.9" +}` +) + type processorTestSuite struct { suite.Suite processor *processor @@ -46,41 +76,20 @@ func (p *processorTestSuite) SetupTest() { p.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: p.regCli} } +func (p *processorTestSuite) TestAbstractMetadata() { + artifact := &artifact.Artifact{} + p.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(0, ioutil.NopCloser(bytes.NewReader([]byte(chartYaml))), nil) + err := p.processor.AbstractMetadata(nil, artifact, []byte(chartManifest)) + p.Require().Nil(err) + p.Equal(icon.DigestOfIconChart, artifact.Icon) + p.regCli.AssertExpectations(p.T()) +} + func (p *processorTestSuite) TestAbstractAddition() { // unknown addition _, err := p.processor.AbstractAddition(nil, nil, "unknown_addition") p.True(errors.IsErr(err, errors.BadRequestCode)) - chartManifest := `{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:76a59ebef39013bf7b57e411629b569a5175590024f31eeaaa577a0f8da9e523","size":528},"layers":[{"mediaType":"application/tar+gzip","digest":"sha256:0bd64cfb958b68c71b46597e22185a41e784dc96e04090bc7d2a480b704c3b65","size":12607}]}` - - chartYaml := `{ - “name”:“redis”, - “home”:“http://redis.io/", - “sources”:[ - “https://github.com/bitnami/bitnami-docker-redis" - -], - “version”:“3.2.5", - “description”:“Open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.“, - “keywords”:[ - “redis”, - “keyvalue”, - “database” - -], - “maintainers”:[ - { - “name”:“bitnami-bot”, - “email”:“containers@bitnami.com" - -} - -], - “icon”:“https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png", - “apiVersion”:“v1”, - “appVersion”:“4.0.9” -}` - chartDetails := &chartserver.VersionDetails{ Dependencies: []*helm_chart.Dependency{ { diff --git a/src/controller/artifact/processor/cnab/cnab.go b/src/controller/artifact/processor/cnab/cnab.go index 7b2605118..41e63ccc1 100644 --- a/src/controller/artifact/processor/cnab/cnab.go +++ b/src/controller/artifact/processor/cnab/cnab.go @@ -19,6 +19,7 @@ import ( ps "github.com/goharbor/harbor/src/controller/artifact/processor" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" ) @@ -46,6 +47,7 @@ type processor struct { } func (p *processor) AbstractMetadata(ctx context.Context, art *artifact.Artifact, manifest []byte) error { + art.Icon = icon.DigestOfIconCNAB cfgManiDgt := "" // try to get the digest of the manifest that the config layer is referenced by for _, reference := range art.References { diff --git a/src/controller/artifact/processor/cnab/cnab_test.go b/src/controller/artifact/processor/cnab/cnab_test.go index 6545717e1..4b1de31b5 100644 --- a/src/controller/artifact/processor/cnab/cnab_test.go +++ b/src/controller/artifact/processor/cnab/cnab_test.go @@ -17,6 +17,7 @@ package cnab import ( "github.com/docker/distribution" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/testing/pkg/registry" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -98,6 +99,7 @@ func (p *processorTestSuite) TestAbstractMetadata() { p.Len(art.ExtraAttrs, 7) p.Equal("0.1.1", art.ExtraAttrs["version"].(string)) p.Equal("helloworld", art.ExtraAttrs["name"].(string)) + p.Equal(icon.DigestOfIconCNAB, art.Icon) } func (p *processorTestSuite) TestGetArtifactType() { diff --git a/src/controller/artifact/processor/default.go b/src/controller/artifact/processor/default.go index 4c51ec68c..228ce1ccd 100644 --- a/src/controller/artifact/processor/default.go +++ b/src/controller/artifact/processor/default.go @@ -20,23 +20,20 @@ import ( "regexp" "strings" + "github.com/docker/distribution/manifest/schema2" + "github.com/goharbor/harbor/src/controller/icon" // annotation parsers will be registered "github.com/goharbor/harbor/src/controller/artifact/annotation" - "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/registry" - - "github.com/docker/distribution/manifest/schema2" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) const ( // ArtifactTypeUnknown defines the type for the unknown artifacts ArtifactTypeUnknown = "UNKNOWN" - // DefaultIconDigest defines default icon layer digest - DefaultIconDigest = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f" ) var ( @@ -126,11 +123,11 @@ func (d *defaultProcessor) AbstractMetadata(ctx context.Context, artifact *artif } if artifact.Icon == "" { - artifact.Icon = DefaultIconDigest + artifact.Icon = icon.DigestOfIconDefault } - return nil } + func (d *defaultProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*Addition, error) { // Addition not support for user-defined artifact yet. // It will be support in the future. diff --git a/src/controller/artifact/processor/default_test.go b/src/controller/artifact/processor/default_test.go index 2bf48525e..163978746 100644 --- a/src/controller/artifact/processor/default_test.go +++ b/src/controller/artifact/processor/default_test.go @@ -16,6 +16,7 @@ package processor import ( "context" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/pkg/distribution" "github.com/goharbor/harbor/src/testing/mock" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -183,7 +184,7 @@ func (d *defaultProcessorTestSuite) TestAbstractMetadata() { d.parser.On("Parse", context.TODO(), mock.AnythingOfType("*artifact.Artifact"), mock.AnythingOfType("[]byte")).Return(nil) err = d.processor.AbstractMetadata(nil, art, content) d.Require().Nil(err) - d.Equal(DefaultIconDigest, art.Icon) + d.Equal(icon.DigestOfIconDefault, art.Icon) } func TestDefaultProcessorTestSuite(t *testing.T) { diff --git a/src/controller/artifact/processor/image/index.go b/src/controller/artifact/processor/image/index.go index dbfe82662..2f0dbdd58 100644 --- a/src/controller/artifact/processor/image/index.go +++ b/src/controller/artifact/processor/image/index.go @@ -16,9 +16,11 @@ package image import ( "context" + "github.com/docker/distribution/manifest/manifestlist" "github.com/goharbor/harbor/src/controller/artifact/processor" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -42,6 +44,14 @@ type indexProcessor struct { *base.IndexProcessor } +func (i *indexProcessor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error { + if err := i.IndexProcessor.AbstractMetadata(ctx, artifact, manifest); err != nil { + return err + } + artifact.Icon = icon.DigestOfIconImage + return nil +} + func (i *indexProcessor) GetArtifactType(ctx context.Context, artifact *artifact.Artifact) string { return ArtifactTypeImage } diff --git a/src/controller/artifact/processor/image/index_test.go b/src/controller/artifact/processor/image/index_test.go index f8393c98a..4fbcc8f97 100644 --- a/src/controller/artifact/processor/image/index_test.go +++ b/src/controller/artifact/processor/image/index_test.go @@ -15,8 +15,11 @@ package image import ( - "github.com/stretchr/testify/suite" "testing" + + "github.com/goharbor/harbor/src/controller/icon" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/stretchr/testify/suite" ) type indexProcessTestSuite struct { @@ -28,6 +31,13 @@ func (i *indexProcessTestSuite) SetupTest() { i.processor = &indexProcessor{} } +func (i *indexProcessTestSuite) TestAbstractMetadata() { + artifact := &artifact.Artifact{} + err := i.processor.AbstractMetadata(nil, artifact, nil) + i.Require().Nil(err) + i.Equal(icon.DigestOfIconImage, artifact.Icon) +} + func (i *indexProcessTestSuite) TestGetArtifactType() { i.Assert().Equal(ArtifactTypeImage, i.processor.GetArtifactType(nil, nil)) } diff --git a/src/controller/artifact/processor/image/manifest_v1.go b/src/controller/artifact/processor/image/manifest_v1.go index 01a1b4db8..4acd90dc5 100644 --- a/src/controller/artifact/processor/image/manifest_v1.go +++ b/src/controller/artifact/processor/image/manifest_v1.go @@ -19,6 +19,7 @@ import ( "encoding/json" "github.com/docker/distribution/manifest/schema1" "github.com/goharbor/harbor/src/controller/artifact/processor" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" @@ -45,6 +46,7 @@ func (m *manifestV1Processor) AbstractMetadata(ctx context.Context, artifact *ar artifact.ExtraAttrs = map[string]interface{}{} } artifact.ExtraAttrs["architecture"] = mani.Architecture + artifact.Icon = icon.DigestOfIconImage return nil } diff --git a/src/controller/artifact/processor/image/manifest_v1_test.go b/src/controller/artifact/processor/image/manifest_v1_test.go index 3aaf9162c..29823feb6 100644 --- a/src/controller/artifact/processor/image/manifest_v1_test.go +++ b/src/controller/artifact/processor/image/manifest_v1_test.go @@ -15,10 +15,12 @@ package image import ( + "testing" + + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/artifact" "github.com/stretchr/testify/suite" - "testing" ) type manifestV1ProcessorTestSuite struct { @@ -81,6 +83,7 @@ func (m *manifestV1ProcessorTestSuite) TestAbstractMetadata() { err := m.processor.AbstractMetadata(nil, artifact, []byte(manifest)) m.Require().Nil(err) m.Assert().Equal("amd64", artifact.ExtraAttrs["architecture"].(string)) + m.Equal(icon.DigestOfIconImage, artifact.Icon) } func (m *manifestV1ProcessorTestSuite) TestAbstractAddition() { diff --git a/src/controller/artifact/processor/image/manifest_v2.go b/src/controller/artifact/processor/image/manifest_v2.go index 8fa3a3bdb..ea236faf2 100644 --- a/src/controller/artifact/processor/image/manifest_v2.go +++ b/src/controller/artifact/processor/image/manifest_v2.go @@ -17,9 +17,11 @@ package image import ( "context" "encoding/json" + "github.com/docker/distribution/manifest/schema2" "github.com/goharbor/harbor/src/controller/artifact/processor" "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/pkg/artifact" @@ -51,6 +53,14 @@ type manifestV2Processor struct { *base.ManifestProcessor } +func (m *manifestV2Processor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error { + if err := m.ManifestProcessor.AbstractMetadata(ctx, artifact, manifest); err != nil { + return err + } + artifact.Icon = icon.DigestOfIconImage + return nil +} + func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) { if addition != AdditionTypeBuildHistory { return nil, errors.New(nil).WithCode(errors.BadRequestCode). diff --git a/src/controller/artifact/processor/image/manifest_v2_test.go b/src/controller/artifact/processor/image/manifest_v2_test.go index 1cb941e69..68b4087aa 100644 --- a/src/controller/artifact/processor/image/manifest_v2_test.go +++ b/src/controller/artifact/processor/image/manifest_v2_test.go @@ -15,16 +15,20 @@ package image import ( - "github.com/docker/distribution" - "github.com/docker/distribution/manifest/schema2" - "github.com/goharbor/harbor/src/controller/artifact/processor/base" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/testing/pkg/registry" - "github.com/stretchr/testify/suite" + "bytes" "io/ioutil" "strings" "testing" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema2" + "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/controller/icon" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/testing/mock" + "github.com/goharbor/harbor/src/testing/pkg/registry" + "github.com/stretchr/testify/suite" ) var ( @@ -135,6 +139,15 @@ func (m *manifestV2ProcessorTestSuite) SetupTest() { m.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: m.regCli} } +func (m *manifestV2ProcessorTestSuite) TestAbstractMetadata() { + artifact := &artifact.Artifact{} + m.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(0, ioutil.NopCloser(bytes.NewReader([]byte(config))), nil) + err := m.processor.AbstractMetadata(nil, artifact, []byte(manifest)) + m.Require().Nil(err) + m.Equal(icon.DigestOfIconImage, artifact.Icon) + m.regCli.AssertExpectations(m.T()) +} + func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() { // unknown addition _, err := m.processor.AbstractAddition(nil, nil, "unknown_addition") diff --git a/src/controller/icon/controller.go b/src/controller/icon/controller.go new file mode 100644 index 000000000..7ea694d28 --- /dev/null +++ b/src/controller/icon/controller.go @@ -0,0 +1,144 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package icon + +import ( + "bytes" + "context" + "encoding/base64" + "image" + // import the gif format + _ "image/gif" + // import the jpeg format + _ "image/jpeg" + "image/png" + "io" + "os" + "sync" + + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/registry" + "github.com/nfnt/resize" +) + +// const definitions +const ( + DigestOfIconImage = "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06" + DigestOfIconChart = "sha256:61cf3a178ff0f75bf08a25d96b75cf7355dc197749a9f128ed3ef34b0df05518" + DigestOfIconCNAB = "sha256:089bdda265c14d8686111402c8ad629e8177a1ceb7dcd0f7f39b6480f623b3bd" + DigestOfIconDefault = "sha256:da834479c923584f4cbcdecc0dac61f32bef1d51e8aae598cf16bd154efab49f" +) + +var ( + builtInIcons = map[string]string{ + DigestOfIconImage: "./icons/image.png", + DigestOfIconChart: "./icons/chart.png", + DigestOfIconCNAB: "./icons/cnab.png", + DigestOfIconDefault: "./icons/default.png", + } + // Ctl is a global icon controller instance + Ctl = NewController() +) + +// Icon model for artifact icon +type Icon struct { + ContentType string + Content string // base64 encoded +} + +// Controller defines the operations related with icon +type Controller interface { + // Get the icon specified by digest + Get(ctx context.Context, digest string) (icon *Icon, err error) +} + +// NewController creates a new instance of the icon controller +func NewController() Controller { + return &controller{ + artMgr: artifact.Mgr, + regCli: registry.Cli, + cache: sync.Map{}, + } +} + +type controller struct { + artMgr artifact.Manager + regCli registry.Client + cache sync.Map +} + +func (c *controller) Get(ctx context.Context, digest string) (*Icon, error) { + ic, exist := c.cache.Load(digest) + if exist { + return ic.(*Icon), nil + } + + var ( + iconFile io.ReadCloser + err error + ) + // for the fixed icons: image, helm chart, CNAB and unknown + if path, exist := builtInIcons[digest]; exist { + iconFile, err = os.Open(path) + if err != nil { + return nil, err + } + defer iconFile.Close() + } else { + // read icon from blob + artifacts, err := c.artMgr.List(ctx, &q.Query{ + Keywords: map[string]interface{}{ + "Icon": digest, + }, + }) + if err != nil { + return nil, err + } + if len(artifacts) == 0 { + return nil, errors.New(nil).WithCode(errors.NotFoundCode). + WithMessage("the icon %s not found", digest) + } + _, iconFile, err = c.regCli.PullBlob(artifacts[0].RepositoryName, digest) + if err != nil { + return nil, err + } + defer iconFile.Close() + } + + img, _, err := image.Decode(iconFile) + if err != nil { + return nil, err + } + + // resize the icon to 50x50 + img = resize.Thumbnail(50, 50, img, resize.NearestNeighbor) + + // encode the resized icon to png + buf := &bytes.Buffer{} + if err = png.Encode(buf, img); err != nil { + return nil, err + } + + icon := &Icon{ + ContentType: "image/png", + Content: base64.StdEncoding.EncodeToString(buf.Bytes()), + } + + c.cache.Store(digest, icon) + + return icon, nil +} diff --git a/src/controller/icon/controller_test.go b/src/controller/icon/controller_test.go new file mode 100644 index 000000000..344ba2892 --- /dev/null +++ b/src/controller/icon/controller_test.go @@ -0,0 +1,82 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package icon + +import ( + "encoding/base64" + "io/ioutil" + "strings" + "testing" + + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/testing/mock" + artifact_testing "github.com/goharbor/harbor/src/testing/pkg/artifact" + "github.com/goharbor/harbor/src/testing/pkg/registry" + "github.com/stretchr/testify/suite" +) + +var ( + // base64 encoded png icon for testing + icon = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAb1BMVEUAAAAkJG0kJFuAgIeDg4MmJmIjI2MnIV4mIWKCgoKAgIQmImCCgoYnI2EkIWKAgIOBgYWBgYOAgoSAgoQnI2EmImAmI2IlImEmIWEmImEmImEmImGAgYQmImGAgYWAgYQmImGAgYQmImGAgYT////3jpbIAAAAInRSTlMABw4gISIkLi8zNDU7QkZGSWdodoSHmp2g1djg4OPj8fT1aPEwQAAAAAFiS0dEJLQG+ZkAAACMSURBVFjD7dfLDoIwEEbho6ioeENQvCP6/u/opiSQNMYBF6b+Zz35Nm0mLY+eIUBAC9jnhgoPMMVQEi4wv3nbApyurvgNsHh6ywDu9WAi4G+Aat0sAliuXMNPgLLjVf5tYDxxDToCtlMQICAQoNo0G2krCzABs4u3FOB4dsVhP/N6AcXO0EE/FgHfBV5wuoevcrdCfQAAAABJRU5ErkJggg==" +) + +type controllerTestSuite struct { + suite.Suite + controller Controller + argMgr *artifact_testing.FakeManager + regCli *registry.FakeClient +} + +func (c *controllerTestSuite) SetupTest() { + c.argMgr = &artifact_testing.FakeManager{} + c.regCli = ®istry.FakeClient{} + c.controller = &controller{ + artMgr: c.argMgr, + regCli: c.regCli, + } +} + +func (c *controllerTestSuite) TestGet() { + // not found + c.argMgr.On("List", mock.Anything, mock.Anything).Return(nil, nil) + _, err := c.controller.Get(nil, "unknown") + c.Require().NotNil(err) + c.True(errors.IsNotFoundErr(err)) + c.argMgr.AssertExpectations(c.T()) + + // reset mocks + c.SetupTest() + + // read icon from blob + c.argMgr.On("List", mock.Anything, mock.Anything).Return([]*artifact.Artifact{ + { + RepositoryName: "library/hello-world", + }, + }, nil) + blob := ioutil.NopCloser(base64.NewDecoder(base64.StdEncoding, strings.NewReader(icon))) + c.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(0, blob, nil) + icon, err := c.controller.Get(nil, "sha256:364feec11702f7ee079ba81da723438373afb0921f3646e9e5015406ee150986") + c.Require().Nil(err) + c.Require().NotNil(icon) + c.Equal("image/png", icon.ContentType) + c.NotEmpty(icon.Content) + c.argMgr.AssertExpectations(c.T()) + c.regCli.AssertExpectations(c.T()) +} + +func TestControllerTestSuite(t *testing.T) { + suite.Run(t, &controllerTestSuite{}) +} diff --git a/src/go.mod b/src/go.mod index 06e7596d1..d17a6134b 100644 --- a/src/go.mod +++ b/src/go.mod @@ -52,6 +52,7 @@ require ( github.com/mattn/go-runewidth v0.0.4 // indirect github.com/miekg/pkcs11 v0.0.0-20170220202408-7283ca79f35e // indirect github.com/ncw/swift v1.0.49 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/olekukonko/tablewriter v0.0.1 github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/image-spec v1.0.1 diff --git a/src/go.sum b/src/go.sum index 390f376a7..d3090e0e6 100644 --- a/src/go.sum +++ b/src/go.sum @@ -594,6 +594,8 @@ github.com/ncw/swift v1.0.49 h1:eQaKIjSt/PXLKfYgzg01nevmO+CMXfXGRhB1gOhDs7E= github.com/ncw/swift v1.0.49/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neo4j-drivers/gobolt v1.7.4/go.mod h1:O9AUbip4Dgre+CD3p40dnMD4a4r52QBIfblg5k7CTbE= github.com/neo4j/neo4j-go-driver v1.7.4/go.mod h1:aPO0vVr+WnhEJne+FgFjfsjzAnssPFLucHgGZ76Zb/U= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= diff --git a/src/pkg/artifact/dao/model.go b/src/pkg/artifact/dao/model.go index e2bb55b4a..06ab67467 100644 --- a/src/pkg/artifact/dao/model.go +++ b/src/pkg/artifact/dao/model.go @@ -36,11 +36,11 @@ type Artifact struct { RepositoryName string `orm:"column(repository_name)"` Digest string `orm:"column(digest)"` Size int64 `orm:"column(size)"` + Icon string `orm:"column(icon)"` PushTime time.Time `orm:"column(push_time)"` PullTime time.Time `orm:"column(pull_time)"` ExtraAttrs string `orm:"column(extra_attrs)"` // json string Annotations string `orm:"column(annotations);type(jsonb)"` // json string - Icon string `orm:"column(icon)"` // icon layer digest } // TableName for artifact diff --git a/src/pkg/artifact/model.go b/src/pkg/artifact/model.go index 757ea68cf..985fbe3e3 100644 --- a/src/pkg/artifact/model.go +++ b/src/pkg/artifact/model.go @@ -38,12 +38,12 @@ type Artifact struct { RepositoryName string `json:"repository_name"` Digest string `json:"digest"` Size int64 `json:"size"` + Icon string `json:"icon"` PushTime time.Time `json:"push_time"` PullTime time.Time `json:"pull_time"` ExtraAttrs map[string]interface{} `json:"extra_attrs"` // only contains the simple attributes specific for the different artifact type, most of them should come from the config layer Annotations map[string]string `json:"annotations"` References []*Reference `json:"references"` // child artifacts referenced by the parent artifact if the artifact is an index - Icon string `json:"icon"` // icon layer digest } // IsImageIndex returns true when artifact is image index @@ -63,6 +63,7 @@ func (a *Artifact) From(art *dao.Artifact) { a.RepositoryName = art.RepositoryName a.Digest = art.Digest a.Size = art.Size + a.Icon = art.Icon a.PushTime = art.PushTime a.PullTime = art.PullTime a.ExtraAttrs = map[string]interface{}{} @@ -91,6 +92,7 @@ func (a *Artifact) To() *dao.Artifact { RepositoryName: a.RepositoryName, Digest: a.Digest, Size: a.Size, + Icon: a.Icon, PushTime: a.PushTime, PullTime: a.PullTime, } diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index b80d58aca..1ca8bc8c0 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -35,6 +35,7 @@ func New() http.Handler { ScanAPI: newScanAPI(), ProjectAPI: newProjectAPI(), PreheatAPI: newPreheatAPI(), + IconAPI: newIconAPI(), }) if err != nil { log.Fatal(err) diff --git a/src/server/v2.0/handler/icon.go b/src/server/v2.0/handler/icon.go new file mode 100644 index 000000000..0f9063390 --- /dev/null +++ b/src/server/v2.0/handler/icon.go @@ -0,0 +1,47 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "context" + + "github.com/go-openapi/runtime/middleware" + "github.com/goharbor/harbor/src/controller/icon" + "github.com/goharbor/harbor/src/server/v2.0/models" + operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/icon" +) + +func newIconAPI() *iconAPI { + return &iconAPI{ + ctl: icon.Ctl, + } +} + +type iconAPI struct { + BaseAPI + ctl icon.Controller +} + +func (i *iconAPI) GetIcon(ctx context.Context, params operation.GetIconParams) middleware.Responder { + icon, err := i.ctl.Get(ctx, params.Digest) + if err != nil { + return i.SendError(ctx, err) + } + + return operation.NewGetIconOK().WithPayload(&models.Icon{ + Content: icon.Content, + ContentType: icon.ContentType, + }) +} diff --git a/src/server/v2.0/handler/model/artifact.go b/src/server/v2.0/handler/model/artifact.go index 9628d0a9f..28c557947 100644 --- a/src/server/v2.0/handler/model/artifact.go +++ b/src/server/v2.0/handler/model/artifact.go @@ -40,6 +40,7 @@ func (a *Artifact) ToSwagger() *models.Artifact { RepositoryID: a.RepositoryID, Digest: a.Digest, Size: a.Size, + Icon: a.Icon, PullTime: strfmt.DateTime(a.PullTime), PushTime: strfmt.DateTime(a.PushTime), ExtraAttrs: a.ExtraAttrs, diff --git a/src/vendor/github.com/nfnt/resize/.travis.yml b/src/vendor/github.com/nfnt/resize/.travis.yml new file mode 100644 index 000000000..5ff08e7e4 --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - "1.x" + - "1.1" + - "1.4" + - "1.10" diff --git a/src/vendor/github.com/nfnt/resize/LICENSE b/src/vendor/github.com/nfnt/resize/LICENSE new file mode 100644 index 000000000..7836cad5f --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/src/vendor/github.com/nfnt/resize/README.md b/src/vendor/github.com/nfnt/resize/README.md new file mode 100644 index 000000000..372777d2e --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/README.md @@ -0,0 +1,151 @@ +# This package is no longer being updated! Please look for alternatives if that bothers you. + +Resize +====== + +Image resizing for the [Go programming language](http://golang.org) with common interpolation methods. + +[![Build Status](https://travis-ci.org/nfnt/resize.svg)](https://travis-ci.org/nfnt/resize) + +Installation +------------ + +```bash +$ go get github.com/nfnt/resize +``` + +It's that easy! + +Usage +----- + +This package needs at least Go 1.1. Import package with + +```go +import "github.com/nfnt/resize" +``` + +The resize package provides 2 functions: + +* `resize.Resize` creates a scaled image with new dimensions (`width`, `height`) using the interpolation function `interp`. + If either `width` or `height` is set to 0, it will be set to an aspect ratio preserving value. +* `resize.Thumbnail` downscales an image preserving its aspect ratio to the maximum dimensions (`maxWidth`, `maxHeight`). + It will return the original image if original sizes are smaller than the provided dimensions. + +```go +resize.Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image +resize.Thumbnail(maxWidth, maxHeight uint, img image.Image, interp resize.InterpolationFunction) image.Image +``` + +The provided interpolation functions are (from fast to slow execution time) + +- `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) +- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) +- `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) +- `MitchellNetravali`: [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514) +- `Lanczos2`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=2 +- `Lanczos3`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=3 + +Which of these methods gives the best results depends on your use case. + +Sample usage: + +```go +package main + +import ( + "github.com/nfnt/resize" + "image/jpeg" + "log" + "os" +) + +func main() { + // open "test.jpg" + file, err := os.Open("test.jpg") + if err != nil { + log.Fatal(err) + } + + // decode jpeg into image.Image + img, err := jpeg.Decode(file) + if err != nil { + log.Fatal(err) + } + file.Close() + + // resize to width 1000 using Lanczos resampling + // and preserve aspect ratio + m := resize.Resize(1000, 0, img, resize.Lanczos3) + + out, err := os.Create("test_resized.jpg") + if err != nil { + log.Fatal(err) + } + defer out.Close() + + // write new image to file + jpeg.Encode(out, m, nil) +} +``` + +Caveats +------- + +* Optimized access routines are used for `image.RGBA`, `image.NRGBA`, `image.RGBA64`, `image.NRGBA64`, `image.YCbCr`, `image.Gray`, and `image.Gray16` types. All other image types are accessed in a generic way that will result in slow processing speed. +* JPEG images are stored in `image.YCbCr`. This image format stores data in a way that will decrease processing speed. A resize may be up to 2 times slower than with `image.RGBA`. + + +Downsizing Samples +------- + +Downsizing is not as simple as it might look like. Images have to be filtered before they are scaled down, otherwise aliasing might occur. +Filtering is highly subjective: Applying too much will blur the whole image, too little will make aliasing become apparent. +Resize tries to provide sane defaults that should suffice in most cases. + +### Artificial sample + +Original image +![Rings](http://nfnt.github.com/img/rings_lg_orig.png) + + + + + + + + + + + + + + +

Nearest-Neighbor

Bilinear

Bicubic

Mitchell-Netravali

Lanczos2

Lanczos3
+ +### Real-Life sample + +Original image +![Original](http://nfnt.github.com/img/IMG_3694_720.jpg) + + + + + + + + + + + + + + +

Nearest-Neighbor

Bilinear

Bicubic

Mitchell-Netravali

Lanczos2

Lanczos3
+ + +License +------- + +Copyright (c) 2012 Jan Schlicht +Resize is released under a MIT style license. diff --git a/src/vendor/github.com/nfnt/resize/converter.go b/src/vendor/github.com/nfnt/resize/converter.go new file mode 100644 index 000000000..f9c520d09 --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/converter.go @@ -0,0 +1,438 @@ +/* +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import "image" + +// Keep value in [0,255] range. +func clampUint8(in int32) uint8 { + // casting a negative int to an uint will result in an overflown + // large uint. this behavior will be exploited here and in other functions + // to achieve a higher performance. + if uint32(in) < 256 { + return uint8(in) + } + if in > 255 { + return 255 + } + return 0 +} + +// Keep value in [0,65535] range. +func clampUint16(in int64) uint16 { + if uint64(in) < 65536 { + return uint16(in) + } + if in > 65535 { + return 65535 + } + return 0 +} + +func resizeGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]int64 + var sum int64 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case xi < 0: + xi = 0 + case xi >= maxX: + xi = maxX + } + + r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() + + rgba[0] += int64(coeff) * int64(r) + rgba[1] += int64(coeff) * int64(g) + rgba[2] += int64(coeff) * int64(b) + rgba[3] += int64(coeff) * int64(a) + sum += int64(coeff) + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + + value := clampUint16(rgba[0] / sum) + out.Pix[offset+0] = uint8(value >> 8) + out.Pix[offset+1] = uint8(value) + value = clampUint16(rgba[1] / sum) + out.Pix[offset+2] = uint8(value >> 8) + out.Pix[offset+3] = uint8(value) + value = clampUint16(rgba[2] / sum) + out.Pix[offset+4] = uint8(value >> 8) + out.Pix[offset+5] = uint8(value) + value = clampUint16(rgba[3] / sum) + out.Pix[offset+6] = uint8(value >> 8) + out.Pix[offset+7] = uint8(value) + } + } +} + +func resizeRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]int32 + var sum int32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 4 + case xi >= maxX: + xi = 4 * maxX + default: + xi = 0 + } + + rgba[0] += int32(coeff) * int32(row[xi+0]) + rgba[1] += int32(coeff) * int32(row[xi+1]) + rgba[2] += int32(coeff) * int32(row[xi+2]) + rgba[3] += int32(coeff) * int32(row[xi+3]) + sum += int32(coeff) + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 + + out.Pix[xo+0] = clampUint8(rgba[0] / sum) + out.Pix[xo+1] = clampUint8(rgba[1] / sum) + out.Pix[xo+2] = clampUint8(rgba[2] / sum) + out.Pix[xo+3] = clampUint8(rgba[3] / sum) + } + } +} + +func resizeNRGBA(in *image.NRGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]int32 + var sum int32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 4 + case xi >= maxX: + xi = 4 * maxX + default: + xi = 0 + } + + // Forward alpha-premultiplication + a := int32(row[xi+3]) + r := int32(row[xi+0]) * a + r /= 0xff + g := int32(row[xi+1]) * a + g /= 0xff + b := int32(row[xi+2]) * a + b /= 0xff + + rgba[0] += int32(coeff) * r + rgba[1] += int32(coeff) * g + rgba[2] += int32(coeff) * b + rgba[3] += int32(coeff) * a + sum += int32(coeff) + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 + + out.Pix[xo+0] = clampUint8(rgba[0] / sum) + out.Pix[xo+1] = clampUint8(rgba[1] / sum) + out.Pix[xo+2] = clampUint8(rgba[2] / sum) + out.Pix[xo+3] = clampUint8(rgba[3] / sum) + } + } +} + +func resizeRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]int64 + var sum int64 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 8 + case xi >= maxX: + xi = 8 * maxX + default: + xi = 0 + } + + rgba[0] += int64(coeff) * (int64(row[xi+0])<<8 | int64(row[xi+1])) + rgba[1] += int64(coeff) * (int64(row[xi+2])<<8 | int64(row[xi+3])) + rgba[2] += int64(coeff) * (int64(row[xi+4])<<8 | int64(row[xi+5])) + rgba[3] += int64(coeff) * (int64(row[xi+6])<<8 | int64(row[xi+7])) + sum += int64(coeff) + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + + value := clampUint16(rgba[0] / sum) + out.Pix[xo+0] = uint8(value >> 8) + out.Pix[xo+1] = uint8(value) + value = clampUint16(rgba[1] / sum) + out.Pix[xo+2] = uint8(value >> 8) + out.Pix[xo+3] = uint8(value) + value = clampUint16(rgba[2] / sum) + out.Pix[xo+4] = uint8(value >> 8) + out.Pix[xo+5] = uint8(value) + value = clampUint16(rgba[3] / sum) + out.Pix[xo+6] = uint8(value >> 8) + out.Pix[xo+7] = uint8(value) + } + } +} + +func resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]int64 + var sum int64 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 8 + case xi >= maxX: + xi = 8 * maxX + default: + xi = 0 + } + + // Forward alpha-premultiplication + a := int64(uint16(row[xi+6])<<8 | uint16(row[xi+7])) + r := int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) * a + r /= 0xffff + g := int64(uint16(row[xi+2])<<8|uint16(row[xi+3])) * a + g /= 0xffff + b := int64(uint16(row[xi+4])<<8|uint16(row[xi+5])) * a + b /= 0xffff + + rgba[0] += int64(coeff) * r + rgba[1] += int64(coeff) * g + rgba[2] += int64(coeff) * b + rgba[3] += int64(coeff) * a + sum += int64(coeff) + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + + value := clampUint16(rgba[0] / sum) + out.Pix[xo+0] = uint8(value >> 8) + out.Pix[xo+1] = uint8(value) + value = clampUint16(rgba[1] / sum) + out.Pix[xo+2] = uint8(value >> 8) + out.Pix[xo+3] = uint8(value) + value = clampUint16(rgba[2] / sum) + out.Pix[xo+4] = uint8(value >> 8) + out.Pix[xo+5] = uint8(value) + value = clampUint16(rgba[3] / sum) + out.Pix[xo+6] = uint8(value >> 8) + out.Pix[xo+7] = uint8(value) + } + } +} + +func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[(x-newBounds.Min.X)*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var gray int32 + var sum int32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case xi < 0: + xi = 0 + case xi >= maxX: + xi = maxX + } + gray += int32(coeff) * int32(row[xi]) + sum += int32(coeff) + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) + out.Pix[offset] = clampUint8(gray / sum) + } + } +} + +func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var gray int64 + var sum int64 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 2 + case xi >= maxX: + xi = 2 * maxX + default: + xi = 0 + } + gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) + sum += int64(coeff) + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 + value := clampUint16(gray / sum) + out.Pix[offset+0] = uint8(value >> 8) + out.Pix[offset+1] = uint8(value) + } + } +} + +func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var p [3]int32 + var sum int32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + coeff := coeffs[ci+i] + if coeff != 0 { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 3 + case xi >= maxX: + xi = 3 * maxX + default: + xi = 0 + } + p[0] += int32(coeff) * int32(row[xi+0]) + p[1] += int32(coeff) * int32(row[xi+1]) + p[2] += int32(coeff) * int32(row[xi+2]) + sum += int32(coeff) + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 + out.Pix[xo+0] = clampUint8(p[0] / sum) + out.Pix[xo+1] = clampUint8(p[1] / sum) + out.Pix[xo+2] = clampUint8(p[2] / sum) + } + } +} + +func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var p [3]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 3 + case xi >= maxX: + xi = 3 * maxX + default: + xi = 0 + } + p[0] += float32(row[xi+0]) + p[1] += float32(row[xi+1]) + p[2] += float32(row[xi+2]) + sum++ + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 + out.Pix[xo+0] = floatToUint8(p[0] / sum) + out.Pix[xo+1] = floatToUint8(p[1] / sum) + out.Pix[xo+2] = floatToUint8(p[2] / sum) + } + } +} diff --git a/src/vendor/github.com/nfnt/resize/filters.go b/src/vendor/github.com/nfnt/resize/filters.go new file mode 100644 index 000000000..4ce04e389 --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/filters.go @@ -0,0 +1,143 @@ +/* +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import ( + "math" +) + +func nearest(in float64) float64 { + if in >= -0.5 && in < 0.5 { + return 1 + } + return 0 +} + +func linear(in float64) float64 { + in = math.Abs(in) + if in <= 1 { + return 1 - in + } + return 0 +} + +func cubic(in float64) float64 { + in = math.Abs(in) + if in <= 1 { + return in*in*(1.5*in-2.5) + 1.0 + } + if in <= 2 { + return in*(in*(2.5-0.5*in)-4.0) + 2.0 + } + return 0 +} + +func mitchellnetravali(in float64) float64 { + in = math.Abs(in) + if in <= 1 { + return (7.0*in*in*in - 12.0*in*in + 5.33333333333) * 0.16666666666 + } + if in <= 2 { + return (-2.33333333333*in*in*in + 12.0*in*in - 20.0*in + 10.6666666667) * 0.16666666666 + } + return 0 +} + +func sinc(x float64) float64 { + x = math.Abs(x) * math.Pi + if x >= 1.220703e-4 { + return math.Sin(x) / x + } + return 1 +} + +func lanczos2(in float64) float64 { + if in > -2 && in < 2 { + return sinc(in) * sinc(in*0.5) + } + return 0 +} + +func lanczos3(in float64) float64 { + if in > -3 && in < 3 { + return sinc(in) * sinc(in*0.3333333333333333) + } + return 0 +} + +// range [-256,256] +func createWeights8(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int16, []int, int) { + filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) + filterFactor := math.Min(1./(blur*scale), 1) + + coeffs := make([]int16, dy*filterLength) + start := make([]int, dy) + for y := 0; y < dy; y++ { + interpX := scale*(float64(y)+0.5) - 0.5 + start[y] = int(interpX) - filterLength/2 + 1 + interpX -= float64(start[y]) + for i := 0; i < filterLength; i++ { + in := (interpX - float64(i)) * filterFactor + coeffs[y*filterLength+i] = int16(kernel(in) * 256) + } + } + + return coeffs, start, filterLength +} + +// range [-65536,65536] +func createWeights16(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int32, []int, int) { + filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) + filterFactor := math.Min(1./(blur*scale), 1) + + coeffs := make([]int32, dy*filterLength) + start := make([]int, dy) + for y := 0; y < dy; y++ { + interpX := scale*(float64(y)+0.5) - 0.5 + start[y] = int(interpX) - filterLength/2 + 1 + interpX -= float64(start[y]) + for i := 0; i < filterLength; i++ { + in := (interpX - float64(i)) * filterFactor + coeffs[y*filterLength+i] = int32(kernel(in) * 65536) + } + } + + return coeffs, start, filterLength +} + +func createWeightsNearest(dy, filterLength int, blur, scale float64) ([]bool, []int, int) { + filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) + filterFactor := math.Min(1./(blur*scale), 1) + + coeffs := make([]bool, dy*filterLength) + start := make([]int, dy) + for y := 0; y < dy; y++ { + interpX := scale*(float64(y)+0.5) - 0.5 + start[y] = int(interpX) - filterLength/2 + 1 + interpX -= float64(start[y]) + for i := 0; i < filterLength; i++ { + in := (interpX - float64(i)) * filterFactor + if in >= -0.5 && in < 0.5 { + coeffs[y*filterLength+i] = true + } else { + coeffs[y*filterLength+i] = false + } + } + } + + return coeffs, start, filterLength +} diff --git a/src/vendor/github.com/nfnt/resize/nearest.go b/src/vendor/github.com/nfnt/resize/nearest.go new file mode 100644 index 000000000..888039d85 --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/nearest.go @@ -0,0 +1,318 @@ +/* +Copyright (c) 2014, Charlie Vieth + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import "image" + +func floatToUint8(x float32) uint8 { + // Nearest-neighbor values are always + // positive no need to check lower-bound. + if x > 0xfe { + return 0xff + } + return uint8(x) +} + +func floatToUint16(x float32) uint16 { + if x > 0xfffe { + return 0xffff + } + return uint16(x) +} + +func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case xi < 0: + xi = 0 + case xi >= maxX: + xi = maxX + } + r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() + rgba[0] += float32(r) + rgba[1] += float32(g) + rgba[2] += float32(b) + rgba[3] += float32(a) + sum++ + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + value := floatToUint16(rgba[0] / sum) + out.Pix[offset+0] = uint8(value >> 8) + out.Pix[offset+1] = uint8(value) + value = floatToUint16(rgba[1] / sum) + out.Pix[offset+2] = uint8(value >> 8) + out.Pix[offset+3] = uint8(value) + value = floatToUint16(rgba[2] / sum) + out.Pix[offset+4] = uint8(value >> 8) + out.Pix[offset+5] = uint8(value) + value = floatToUint16(rgba[3] / sum) + out.Pix[offset+6] = uint8(value >> 8) + out.Pix[offset+7] = uint8(value) + } + } +} + +func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 4 + case xi >= maxX: + xi = 4 * maxX + default: + xi = 0 + } + rgba[0] += float32(row[xi+0]) + rgba[1] += float32(row[xi+1]) + rgba[2] += float32(row[xi+2]) + rgba[3] += float32(row[xi+3]) + sum++ + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 + out.Pix[xo+0] = floatToUint8(rgba[0] / sum) + out.Pix[xo+1] = floatToUint8(rgba[1] / sum) + out.Pix[xo+2] = floatToUint8(rgba[2] / sum) + out.Pix[xo+3] = floatToUint8(rgba[3] / sum) + } + } +} + +func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 4 + case xi >= maxX: + xi = 4 * maxX + default: + xi = 0 + } + rgba[0] += float32(row[xi+0]) + rgba[1] += float32(row[xi+1]) + rgba[2] += float32(row[xi+2]) + rgba[3] += float32(row[xi+3]) + sum++ + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 + out.Pix[xo+0] = floatToUint8(rgba[0] / sum) + out.Pix[xo+1] = floatToUint8(rgba[1] / sum) + out.Pix[xo+2] = floatToUint8(rgba[2] / sum) + out.Pix[xo+3] = floatToUint8(rgba[3] / sum) + } + } +} + +func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 8 + case xi >= maxX: + xi = 8 * maxX + default: + xi = 0 + } + rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) + rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) + rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) + rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) + sum++ + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + value := floatToUint16(rgba[0] / sum) + out.Pix[xo+0] = uint8(value >> 8) + out.Pix[xo+1] = uint8(value) + value = floatToUint16(rgba[1] / sum) + out.Pix[xo+2] = uint8(value >> 8) + out.Pix[xo+3] = uint8(value) + value = floatToUint16(rgba[2] / sum) + out.Pix[xo+4] = uint8(value >> 8) + out.Pix[xo+5] = uint8(value) + value = floatToUint16(rgba[3] / sum) + out.Pix[xo+6] = uint8(value >> 8) + out.Pix[xo+7] = uint8(value) + } + } +} + +func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var rgba [4]float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 8 + case xi >= maxX: + xi = 8 * maxX + default: + xi = 0 + } + rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) + rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) + rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) + rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) + sum++ + } + } + + xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 + value := floatToUint16(rgba[0] / sum) + out.Pix[xo+0] = uint8(value >> 8) + out.Pix[xo+1] = uint8(value) + value = floatToUint16(rgba[1] / sum) + out.Pix[xo+2] = uint8(value >> 8) + out.Pix[xo+3] = uint8(value) + value = floatToUint16(rgba[2] / sum) + out.Pix[xo+4] = uint8(value >> 8) + out.Pix[xo+5] = uint8(value) + value = floatToUint16(rgba[3] / sum) + out.Pix[xo+6] = uint8(value >> 8) + out.Pix[xo+7] = uint8(value) + } + } +} + +func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var gray float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case xi < 0: + xi = 0 + case xi >= maxX: + xi = maxX + } + gray += float32(row[xi]) + sum++ + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) + out.Pix[offset] = floatToUint8(gray / sum) + } + } +} + +func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) { + newBounds := out.Bounds() + maxX := in.Bounds().Dx() - 1 + + for x := newBounds.Min.X; x < newBounds.Max.X; x++ { + row := in.Pix[x*in.Stride:] + for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { + var gray float32 + var sum float32 + start := offset[y] + ci := y * filterLength + for i := 0; i < filterLength; i++ { + if coeffs[ci+i] { + xi := start + i + switch { + case uint(xi) < uint(maxX): + xi *= 2 + case xi >= maxX: + xi = 2 * maxX + default: + xi = 0 + } + gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) + sum++ + } + } + + offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 + value := floatToUint16(gray / sum) + out.Pix[offset+0] = uint8(value >> 8) + out.Pix[offset+1] = uint8(value) + } + } +} diff --git a/src/vendor/github.com/nfnt/resize/resize.go b/src/vendor/github.com/nfnt/resize/resize.go new file mode 100644 index 000000000..0d7fbf69a --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/resize.go @@ -0,0 +1,620 @@ +/* +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +// Package resize implements various image resizing methods. +// +// The package works with the Image interface described in the image package. +// Various interpolation methods are provided and multiple processors may be +// utilized in the computations. +// +// Example: +// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali) +package resize + +import ( + "image" + "runtime" + "sync" +) + +// An InterpolationFunction provides the parameters that describe an +// interpolation kernel. It returns the number of samples to take +// and the kernel function to use for sampling. +type InterpolationFunction int + +// InterpolationFunction constants +const ( + // Nearest-neighbor interpolation + NearestNeighbor InterpolationFunction = iota + // Bilinear interpolation + Bilinear + // Bicubic interpolation (with cubic hermite spline) + Bicubic + // Mitchell-Netravali interpolation + MitchellNetravali + // Lanczos interpolation (a=2) + Lanczos2 + // Lanczos interpolation (a=3) + Lanczos3 +) + +// kernal, returns an InterpolationFunctions taps and kernel. +func (i InterpolationFunction) kernel() (int, func(float64) float64) { + switch i { + case Bilinear: + return 2, linear + case Bicubic: + return 4, cubic + case MitchellNetravali: + return 4, mitchellnetravali + case Lanczos2: + return 4, lanczos2 + case Lanczos3: + return 6, lanczos3 + default: + // Default to NearestNeighbor. + return 2, nearest + } +} + +// values <1 will sharpen the image +var blur = 1.0 + +// Resize scales an image to new width and height using the interpolation function interp. +// A new image with the given dimensions will be returned. +// If one of the parameters width or height is set to 0, its size will be calculated so that +// the aspect ratio is that of the originating image. +// The resizing algorithm uses channels for parallel computation. +// If the input image has width or height of 0, it is returned unchanged. +func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image { + scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy())) + if width == 0 { + width = uint(0.7 + float64(img.Bounds().Dx())/scaleX) + } + if height == 0 { + height = uint(0.7 + float64(img.Bounds().Dy())/scaleY) + } + + // Trivial case: return input image + if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() { + return img + } + + // Input image has no pixels + if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 { + return img + } + + if interp == NearestNeighbor { + return resizeNearest(width, height, scaleX, scaleY, img, interp) + } + + taps, kernel := interp.kernel() + cpus := runtime.GOMAXPROCS(0) + wg := sync.WaitGroup{} + + // Generic access to image.Image is slow in tight loops. + // The optimal access has to be determined from the concrete image type. + switch input := img.(type) { + case *image.RGBA: + // 8-bit precision + temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.NRGBA: + // 8-bit precision + temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + + case *image.YCbCr: + // 8-bit precision + // accessing the YCbCr arrays in a tight loop is slow. + // converting the image to ycc increases performance by 2x. + temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) + result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) + + coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + in := imageYCbCrToYCC(input) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*ycc) + go func() { + defer wg.Done() + resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*ycc) + go func() { + defer wg.Done() + resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result.YCbCr() + case *image.RGBA64: + // 16-bit precision + temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.NRGBA64: + // 16-bit precision + temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.Gray: + // 8-bit precision + temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewGray(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.Gray) + go func() { + defer wg.Done() + resizeGray(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.Gray) + go func() { + defer wg.Done() + resizeGray(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.Gray16: + // 16-bit precision + temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.Gray16) + go func() { + defer wg.Done() + resizeGray16(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.Gray16) + go func() { + defer wg.Done() + resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + default: + // 16-bit precision + temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + } +} + +func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image { + taps, _ := interp.kernel() + cpus := runtime.GOMAXPROCS(0) + wg := sync.WaitGroup{} + + switch input := img.(type) { + case *image.RGBA: + // 8-bit precision + temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA) + go func() { + defer wg.Done() + nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.NRGBA: + // 8-bit precision + temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.NRGBA) + go func() { + defer wg.Done() + nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.NRGBA) + go func() { + defer wg.Done() + nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.YCbCr: + // 8-bit precision + // accessing the YCbCr arrays in a tight loop is slow. + // converting the image to ycc increases performance by 2x. + temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) + result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) + + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + in := imageYCbCrToYCC(input) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*ycc) + go func() { + defer wg.Done() + nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*ycc) + go func() { + defer wg.Done() + nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result.YCbCr() + case *image.RGBA64: + // 16-bit precision + temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.NRGBA64: + // 16-bit precision + temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.NRGBA64) + go func() { + defer wg.Done() + nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.NRGBA64) + go func() { + defer wg.Done() + nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.Gray: + // 8-bit precision + temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewGray(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.Gray) + go func() { + defer wg.Done() + nearestGray(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.Gray) + go func() { + defer wg.Done() + nearestGray(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + case *image.Gray16: + // 16-bit precision + temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.Gray16) + go func() { + defer wg.Done() + nearestGray16(input, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.Gray16) + go func() { + defer wg.Done() + nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + default: + // 16-bit precision + temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) + + // horizontal filter, results in transposed temporary image + coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(temp, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength) + }() + } + wg.Wait() + + // horizontal filter on transposed image, result is not transposed + coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) + wg.Add(cpus) + for i := 0; i < cpus; i++ { + slice := makeSlice(result, i, cpus).(*image.RGBA64) + go func() { + defer wg.Done() + nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + }() + } + wg.Wait() + return result + } + +} + +// Calculates scaling factors using old and new image dimensions. +func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) { + if width == 0 { + if height == 0 { + scaleX = 1.0 + scaleY = 1.0 + } else { + scaleY = oldHeight / float64(height) + scaleX = scaleY + } + } else { + scaleX = oldWidth / float64(width) + if height == 0 { + scaleY = scaleX + } else { + scaleY = oldHeight / float64(height) + } + } + return +} + +type imageWithSubImage interface { + image.Image + SubImage(image.Rectangle) image.Image +} + +func makeSlice(img imageWithSubImage, i, n int) image.Image { + return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n)) +} diff --git a/src/vendor/github.com/nfnt/resize/thumbnail.go b/src/vendor/github.com/nfnt/resize/thumbnail.go new file mode 100644 index 000000000..9efc246be --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/thumbnail.go @@ -0,0 +1,55 @@ +/* +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import ( + "image" +) + +// Thumbnail will downscale provided image to max width and height preserving +// original aspect ratio and using the interpolation function interp. +// It will return original image, without processing it, if original sizes +// are already smaller than provided constraints. +func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image { + origBounds := img.Bounds() + origWidth := uint(origBounds.Dx()) + origHeight := uint(origBounds.Dy()) + newWidth, newHeight := origWidth, origHeight + + // Return original image if it have same or smaller size as constraints + if maxWidth >= origWidth && maxHeight >= origHeight { + return img + } + + // Preserve aspect ratio + if origWidth > maxWidth { + newHeight = uint(origHeight * maxWidth / origWidth) + if newHeight < 1 { + newHeight = 1 + } + newWidth = maxWidth + } + + if newHeight > maxHeight { + newWidth = uint(newWidth * maxHeight / newHeight) + if newWidth < 1 { + newWidth = 1 + } + newHeight = maxHeight + } + return Resize(newWidth, newHeight, img, interp) +} diff --git a/src/vendor/github.com/nfnt/resize/ycc.go b/src/vendor/github.com/nfnt/resize/ycc.go new file mode 100644 index 000000000..143e4d06a --- /dev/null +++ b/src/vendor/github.com/nfnt/resize/ycc.go @@ -0,0 +1,387 @@ +/* +Copyright (c) 2014, Charlie Vieth + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import ( + "image" + "image/color" +) + +// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a +// single slice to increase resizing performance. +type ycc struct { + // Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. + Stride int + // Rect is the image's bounds. + Rect image.Rectangle + // SubsampleRatio is the subsample ratio of the original YCbCr image. + SubsampleRatio image.YCbCrSubsampleRatio +} + +// PixOffset returns the index of the first element of Pix that corresponds to +// the pixel at (x, y). +func (p *ycc) PixOffset(x, y int) int { + return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 +} + +func (p *ycc) Bounds() image.Rectangle { + return p.Rect +} + +func (p *ycc) ColorModel() color.Model { + return color.YCbCrModel +} + +func (p *ycc) At(x, y int) color.Color { + if !(image.Point{x, y}.In(p.Rect)) { + return color.YCbCr{} + } + i := p.PixOffset(x, y) + return color.YCbCr{ + p.Pix[i+0], + p.Pix[i+1], + p.Pix[i+2], + } +} + +func (p *ycc) Opaque() bool { + return true +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *ycc) SubImage(r image.Rectangle) image.Image { + r = r.Intersect(p.Rect) + if r.Empty() { + return &ycc{SubsampleRatio: p.SubsampleRatio} + } + i := p.PixOffset(r.Min.X, r.Min.Y) + return &ycc{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + SubsampleRatio: p.SubsampleRatio, + } +} + +// newYCC returns a new ycc with the given bounds and subsample ratio. +func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc { + w, h := r.Dx(), r.Dy() + buf := make([]uint8, 3*w*h) + return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s} +} + +// Copy of image.YCbCrSubsampleRatio constants - this allows us to support +// older versions of Go where these constants are not defined (i.e. Go 1.4) +const ( + ycbcrSubsampleRatio444 image.YCbCrSubsampleRatio = iota + ycbcrSubsampleRatio422 + ycbcrSubsampleRatio420 + ycbcrSubsampleRatio440 + ycbcrSubsampleRatio411 + ycbcrSubsampleRatio410 +) + +// YCbCr converts ycc to a YCbCr image with the same subsample ratio +// as the YCbCr image that ycc was generated from. +func (p *ycc) YCbCr() *image.YCbCr { + ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio) + switch ycbcr.SubsampleRatio { + case ycbcrSubsampleRatio422: + return p.ycbcr422(ycbcr) + case ycbcrSubsampleRatio420: + return p.ycbcr420(ycbcr) + case ycbcrSubsampleRatio440: + return p.ycbcr440(ycbcr) + case ycbcrSubsampleRatio444: + return p.ycbcr444(ycbcr) + case ycbcrSubsampleRatio411: + return p.ycbcr411(ycbcr) + case ycbcrSubsampleRatio410: + return p.ycbcr410(ycbcr) + } + return ycbcr +} + +// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing. +func imageYCbCrToYCC(in *image.YCbCr) *ycc { + w, h := in.Rect.Dx(), in.Rect.Dy() + p := ycc{ + Pix: make([]uint8, 3*w*h), + Stride: 3 * w, + Rect: image.Rect(0, 0, w, h), + SubsampleRatio: in.SubsampleRatio, + } + switch in.SubsampleRatio { + case ycbcrSubsampleRatio422: + return convertToYCC422(in, &p) + case ycbcrSubsampleRatio420: + return convertToYCC420(in, &p) + case ycbcrSubsampleRatio440: + return convertToYCC440(in, &p) + case ycbcrSubsampleRatio444: + return convertToYCC444(in, &p) + case ycbcrSubsampleRatio411: + return convertToYCC411(in, &p) + case ycbcrSubsampleRatio410: + return convertToYCC410(in, &p) + } + return &p +} + +func (p *ycc) ycbcr422(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := y * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x/2 + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func (p *ycc) ycbcr420(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := (y / 2) * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x/2 + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func (p *ycc) ycbcr440(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := (y / 2) * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func (p *ycc) ycbcr444(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := y * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func (p *ycc) ycbcr411(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := y * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x/4 + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func (p *ycc) ycbcr410(ycbcr *image.YCbCr) *image.YCbCr { + var off int + Pix := p.Pix + Y := ycbcr.Y + Cb := ycbcr.Cb + Cr := ycbcr.Cr + for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { + yy := y * ycbcr.YStride + cy := (y / 2) * ycbcr.CStride + for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { + ci := cy + x/4 + Y[yy+x] = Pix[off+0] + Cb[ci] = Pix[off+1] + Cr[ci] = Pix[off+2] + off += 3 + } + } + return ycbcr +} + +func convertToYCC422(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := y * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x/2 + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} + +func convertToYCC420(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := (y / 2) * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x/2 + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} + +func convertToYCC440(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := (y / 2) * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} + +func convertToYCC444(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := y * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} + +func convertToYCC411(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := y * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x/4 + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} + +func convertToYCC410(in *image.YCbCr, p *ycc) *ycc { + var off int + Pix := p.Pix + Y := in.Y + Cb := in.Cb + Cr := in.Cr + for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { + yy := y * in.YStride + cy := (y / 2) * in.CStride + for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { + ci := cy + x/4 + Pix[off+0] = Y[yy+x] + Pix[off+1] = Cb[ci] + Pix[off+2] = Cr[ci] + off += 3 + } + } + return p +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 214fffd5a..b9447ced5 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -398,6 +398,9 @@ github.com/modern-go/reflect2 # github.com/ncw/swift v1.0.49 ## explicit github.com/ncw/swift +# github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 +## explicit +github.com/nfnt/resize # github.com/olekukonko/tablewriter v0.0.1 ## explicit github.com/olekukonko/tablewriter