From 6ff26778651145c99e74eaec862a04925ac6ea69 Mon Sep 17 00:00:00 2001 From: Saman Hosseini Date: Mon, 5 Sep 2022 17:48:05 +0200 Subject: [PATCH] feat(image): add result utilities --- image/compress.go | 53 +++++++++++++++++++++++++++++++++++ image/pipeline.go | 63 ++++++++++++++++++++++++++++++++++++------ image/pipeline_test.go | 16 +++++++++++ image/resize.go | 11 ++++++++ 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/image/compress.go b/image/compress.go index a680d3b..87b72a5 100644 --- a/image/compress.go +++ b/image/compress.go @@ -2,6 +2,8 @@ package image import ( "image" + "strconv" + "strings" "github.com/modernice/media-tools/image/internal" ) @@ -112,3 +114,54 @@ func (c *Compressor) Process(ctx ProcessorContext) ([]Processed, error) { return out, nil } + +// CompressionName extracts the name of the [Compression] from the tags of a +// processed image. +func CompressionName(tags Tags) string { + if !tags.Contains(Compressed) { + return "" + } + + for _, tag := range tags { + if !strings.HasPrefix(tag, "compression=") { + continue + } + + parts := strings.Split(tag, ",") + compressionTag := parts[0] + + return compressionTag[12:] + } + + return "" +} + +// CompressionQuality extracts the name of the compression quality from the tags +// of a processed image. If the tags to not provide the compression quality, -1 +// is returned. +func CompressionQuality(tags Tags) int { + if !tags.Contains(Compressed) { + return -1 + } + + for _, tag := range tags { + if !strings.HasPrefix(tag, "compression=") { + continue + } + + parts := strings.Split(tag, ",") + if len(parts) < 2 { + return -1 + } + + rawQuality := parts[1] + quality, err := strconv.Atoi(rawQuality[8:]) + if err != nil { + return -1 + } + + return quality + } + + return -1 +} diff --git a/image/pipeline.go b/image/pipeline.go index 9d4a908..5cd8131 100644 --- a/image/pipeline.go +++ b/image/pipeline.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "image" - "strings" + "regexp" "github.com/modernice/media-tools/internal/slices" ) @@ -62,6 +62,9 @@ func (ctx *processorContext) Image() Processed { type PipelineResult struct { // Images are the processed images. Images []Processed + + // Input is the original image that was passed to the [Pipeline]. + Input image.Image } // Tags is a list of tags that Processors assigned to images in a [Pipeline]. @@ -72,6 +75,17 @@ func NewTags(tags ...string) Tags { return slices.Unique(Tags(tags)) } +// Match returns the tags that match the given regular expression. +func (tags Tags) Match(re *regexp.Regexp) []string { + var out []string + for _, tag := range tags { + if re.MatchString(tag) { + out = append(out, tag) + } + } + return out +} + // Contains returns whether a tag is contained within tags. func (tags Tags) Contains(tag string) bool { return slices.Contains(tag, tags) @@ -97,7 +111,7 @@ func (tags Tags) Without(remove ...string) Tags { // Run runs the pipeline on an image and returns the [PipelineResult], // containing the processed images. func (pipeline Pipeline) Run(ctx context.Context, img image.Image) (PipelineResult, error) { - previous := []Processed{{Image: img, Original: true, Tags: NewTags(Original)}} + previous := []Processed{{Image: img, Tags: NewTags(Original), Original: true}} for _, processor := range pipeline { _previous := previous @@ -126,15 +140,46 @@ func (pipeline Pipeline) Run(ctx context.Context, img image.Image) (PipelineResu } } - return PipelineResult{Images: previous}, nil + return PipelineResult{ + Images: previous, + Input: img, + }, nil } -// DimensionName extracts the dimension name from the tags of a processed image. -func DimensionName(tags Tags) string { - for _, tag := range tags { - if strings.HasPrefix(tag, "size=") { - return tag[5:] +// Original returns the processed image that is tagged as the original image. +// Depending on the [Pipeline], the original image may have been transformed +// by one or more [Processor]s. [PipelineResult.Input] is the actual image that +// was passed to [Pipeline.Run]. +// +// If the [Pipeline] discarded the original image from its result, false is returned. +func (result PipelineResult) Original() (Processed, bool) { + images := result.Find(Original) + if len(images) > 0 { + return images[0], true + } + return Processed{}, false +} + +// Find returns the processed images that gave the given tag. +func (result PipelineResult) Find(tag string) []Processed { + var out []Processed + for _, img := range result.Images { + if img.Tags.Contains(tag) { + out = append(out, img) + } + } + return out +} + +// Match returns the processed images that have at least 1 tag that matches the +// given regular expression. +func (result PipelineResult) Match(re *regexp.Regexp) []Processed { + var out []Processed + for _, img := range result.Images { + matched := img.Tags.Match(re) + if len(matched) > 0 { + out = append(out, img) } } - return "" + return out } diff --git a/image/pipeline_test.go b/image/pipeline_test.go index cc48da8..065f3fd 100644 --- a/image/pipeline_test.go +++ b/image/pipeline_test.go @@ -74,6 +74,14 @@ func TestPipeline_Run(t *testing.T) { t.Fatalf("second image should have tag %q", "compression=jpeg,quality=75") } + if image.CompressionName(result.Images[1].Tags) != "jpeg" { + t.Fatalf("second image should have compression tag %q", "jpeg") + } + + if image.CompressionQuality(result.Images[1].Tags) != 75 { + t.Fatalf("second image should have compression quality %d", 75) + } + lastIdx := len(result.Images) - 1 secondLastIdx := len(result.Images) - 2 @@ -109,6 +117,14 @@ func TestPipeline_Run(t *testing.T) { t.Fatalf("last image should have tag %q", "compression=jpeg,quality=50") } + if image.CompressionName(result.Images[lastIdx].Tags) != "jpeg" { + t.Fatalf("last image should have compression tag %q", "jpeg") + } + + if image.CompressionQuality(result.Images[lastIdx].Tags) != 50 { + t.Fatalf("last image should have compression quality %d", 50) + } + for _, img := range result.Images { if !img.Tags.Contains("foo") { t.Fatalf("all images should have tag %q", "foo") diff --git a/image/resize.go b/image/resize.go index 6e9dff9..f379e23 100644 --- a/image/resize.go +++ b/image/resize.go @@ -3,6 +3,7 @@ package image import ( "fmt" "image" + "strings" "github.com/disintegration/imaging" "github.com/modernice/media-tools/internal/slices" @@ -132,3 +133,13 @@ func (r *Resizer) Process(ctx ProcessorContext) ([]Processed, error) { return append([]Processed{ctx.Image()}, processed...), nil } + +// DimensionName extracts the dimension name from the tags of a processed image. +func DimensionName(tags Tags) string { + for _, tag := range tags { + if strings.HasPrefix(tag, "size=") { + return tag[5:] + } + } + return "" +}