From ddb72a6cc26dc7a9a0c259912d1a069eeb464011 Mon Sep 17 00:00:00 2001 From: Matt Sanford Date: Fri, 14 Mar 2014 14:18:11 -0700 Subject: [PATCH 1/2] Add grayscale processor --- examples/filesystem_config.json | 3 ++- halfshell/config.go | 2 ++ halfshell/image_processor.go | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/filesystem_config.json b/examples/filesystem_config.json index 83d49fe..cfeff13 100644 --- a/examples/filesystem_config.json +++ b/examples/filesystem_config.json @@ -26,7 +26,8 @@ }, "profile-photos": { - "default_image_width": 120 + "default_image_width": 120, + "grayscale": true } }, "routes": { diff --git a/halfshell/config.go b/halfshell/config.go index 1d9c554..69c9aa7 100644 --- a/halfshell/config.go +++ b/halfshell/config.go @@ -73,6 +73,7 @@ type ProcessorConfig struct { MaxImageHeight uint64 MaxImageWidth uint64 MaxBlurRadiusPercentage float64 + Grayscale bool } // Parses a JSON configuration file and returns a pointer to a new Config object. @@ -176,6 +177,7 @@ func (c *configParser) parseProcessorConfig(processorName string) *ProcessorConf MaxImageHeight: c.uintForKeypath("processors.%s.max_image_height", processorName), MaxImageWidth: c.uintForKeypath("processors.%s.max_image_width", processorName), MaxBlurRadiusPercentage: c.floatForKeypath("processors.%s.max_blur_radius_percentage", processorName), + Grayscale: c.boolForKeypath("processors.%s.grayscale", processorName), } } diff --git a/halfshell/image_processor.go b/halfshell/image_processor.go index 3f7989e..0b3ea31 100644 --- a/halfshell/image_processor.go +++ b/halfshell/image_processor.go @@ -73,7 +73,13 @@ func (ip *imageProcessor) ProcessImage(image *Image, request *ImageProcessorOpti return nil } - if !scaleModified && !blurModified { + err, grayscaleModified := ip.grayscaleWand(wand, request) + if err != nil { + ip.Logger.Warn("Error grayscaling image: %s", err) + return nil + } + + if !scaleModified && !blurModified && !grayscaleModified { processedImage.Bytes = image.Bytes } else { processedImage.Bytes = wand.GetImageBlob() @@ -138,6 +144,16 @@ func (ip *imageProcessor) blurWand(wand *imagick.MagickWand, request *ImageProce return nil, false } +func (ip *imageProcessor) grayscaleWand(wand *imagick.MagickWand, request *ImageProcessorOptions) (err error, modified bool) { + if ip.Config.Grayscale { + if err = wand.TransformImageColorspace(imagick.COLORSPACE_GRAY); err != nil { + ip.Logger.Warn("ImageMagick error grayscaling image: %s", err) + } + return err, true + } + return nil, false +} + func (ip *imageProcessor) getScaledDimensions(currentDimensions ImageDimensions, request *ImageProcessorOptions) ImageDimensions { requestDimensions := request.Dimensions if requestDimensions.Width == 0 && requestDimensions.Height == 0 { From 6ad9a8f099423aec986bc8f9cd31da042c790cde Mon Sep 17 00:00:00 2001 From: Matt Sanford Date: Wed, 2 Apr 2014 13:36:00 -0700 Subject: [PATCH 2/2] Add support for grayscale via URL param. Bonus: Add option to turn off statsd --- examples/filesystem_config.json | 7 ++++--- halfshell/config.go | 37 +++++++++++++++++++++++++-------- halfshell/image_processor.go | 3 ++- halfshell/route.go | 2 ++ halfshell/server.go | 7 +++++-- halfshell/source_filesystem.go | 4 ++-- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/examples/filesystem_config.json b/examples/filesystem_config.json index cfeff13..0f635f4 100644 --- a/examples/filesystem_config.json +++ b/examples/filesystem_config.json @@ -2,7 +2,8 @@ "server": { "port": 8080, "read_timeout": 5, - "write_timeout": 30 + "write_timeout": 30, + "disable_statsd": true }, "sources": { "default": { @@ -22,8 +23,8 @@ "maintain_aspect_ratio": true, "max_blur_radius_percentage": 0, "max_image_height": 0, - "max_image_width": 1000 - + "max_image_width": 1000, + "grayscale": "disabled" }, "profile-photos": { "default_image_width": 120, diff --git a/halfshell/config.go b/halfshell/config.go index 69c9aa7..3be9bdd 100644 --- a/halfshell/config.go +++ b/halfshell/config.go @@ -38,9 +38,10 @@ type Config struct { // ServerConfig holds the configuration settings relevant for the HTTP server. type ServerConfig struct { - Port uint64 - ReadTimeout uint64 - WriteTimeout uint64 + Port uint64 + ReadTimeout uint64 + WriteTimeout uint64 + StatsdDisabled bool } // RouteConfig holds the configuration settings for a particular route. @@ -73,7 +74,8 @@ type ProcessorConfig struct { MaxImageHeight uint64 MaxImageWidth uint64 MaxBlurRadiusPercentage float64 - Grayscale bool + GrayscaleByDefault bool + GrayscaleDisabled bool } // Parses a JSON configuration file and returns a pointer to a new Config object. @@ -150,9 +152,10 @@ func (c *configParser) parse() *Config { func (c *configParser) parseServerConfig() *ServerConfig { return &ServerConfig{ - Port: c.uintForKeypath("server.port"), - ReadTimeout: c.uintForKeypath("server.read_timeout"), - WriteTimeout: c.uintForKeypath("server.write_timeout"), + Port: c.uintForKeypath("server.port"), + ReadTimeout: c.uintForKeypath("server.read_timeout"), + WriteTimeout: c.uintForKeypath("server.write_timeout"), + StatsdDisabled: c.boolForKeypath("server.disable_statsd"), } } @@ -168,7 +171,7 @@ func (c *configParser) parseSourceConfig(sourceName string) *SourceConfig { } func (c *configParser) parseProcessorConfig(processorName string) *ProcessorConfig { - return &ProcessorConfig{ + config := &ProcessorConfig{ Name: processorName, ImageCompressionQuality: c.uintForKeypath("processors.%s.image_compression_quality", processorName), MaintainAspectRatio: c.boolForKeypath("processors.%s.maintain_aspect_ratio", processorName), @@ -177,8 +180,11 @@ func (c *configParser) parseProcessorConfig(processorName string) *ProcessorConf MaxImageHeight: c.uintForKeypath("processors.%s.max_image_height", processorName), MaxImageWidth: c.uintForKeypath("processors.%s.max_image_width", processorName), MaxBlurRadiusPercentage: c.floatForKeypath("processors.%s.max_blur_radius_percentage", processorName), - Grayscale: c.boolForKeypath("processors.%s.grayscale", processorName), } + + config.GrayscaleByDefault, config.GrayscaleDisabled = c.onOrDisabledForKeypath("processors.%s.grayscale", processorName) + + return config } func (c *configParser) valueForKeypath(valueType reflect.Kind, keypathFormat string, v ...interface{}) interface{} { @@ -228,3 +234,16 @@ func (c *configParser) uintForKeypath(keypathFormat string, v ...interface{}) ui func (c *configParser) boolForKeypath(keypathFormat string, v ...interface{}) bool { return c.valueForKeypath(reflect.Bool, keypathFormat, v...).(bool) } + +func (c *configParser) onOrDisabledForKeypath(keypathFormat string, v ...interface{}) (bool, bool) { + value := c.valueForKeypath(reflect.String, keypathFormat, v...) + switch value.(type) { + case bool: + return value.(bool), false + case string: + if value == "disabled" { + return false, true + } + } + return false, false +} diff --git a/halfshell/image_processor.go b/halfshell/image_processor.go index 0b3ea31..e06ce7a 100644 --- a/halfshell/image_processor.go +++ b/halfshell/image_processor.go @@ -38,6 +38,7 @@ type ImageProcessor interface { type ImageProcessorOptions struct { Dimensions ImageDimensions BlurRadius float64 + GrayScale bool } type imageProcessor struct { @@ -145,7 +146,7 @@ func (ip *imageProcessor) blurWand(wand *imagick.MagickWand, request *ImageProce } func (ip *imageProcessor) grayscaleWand(wand *imagick.MagickWand, request *ImageProcessorOptions) (err error, modified bool) { - if ip.Config.Grayscale { + if !ip.Config.GrayscaleDisabled && (ip.Config.GrayscaleByDefault || request.GrayScale) { if err = wand.TransformImageColorspace(imagick.COLORSPACE_GRAY); err != nil { ip.Logger.Warn("ImageMagick error grayscaling image: %s", err) } diff --git a/halfshell/route.go b/halfshell/route.go index 973f2e4..0446373 100644 --- a/halfshell/route.go +++ b/halfshell/route.go @@ -68,9 +68,11 @@ func (p *Route) SourceAndProcessorOptionsForRequest(r *http.Request) ( width, _ := strconv.ParseUint(r.FormValue("w"), 10, 32) height, _ := strconv.ParseUint(r.FormValue("h"), 10, 32) blurRadius, _ := strconv.ParseFloat(r.FormValue("blur"), 64) + grayScale, _ := strconv.ParseBool(r.FormValue("grayscale")) return &ImageSourceOptions{Path: path}, &ImageProcessorOptions{ Dimensions: ImageDimensions{width, height}, BlurRadius: blurRadius, + GrayScale: grayScale, } } diff --git a/halfshell/server.go b/halfshell/server.go index 100bda5..01a4181 100644 --- a/halfshell/server.go +++ b/halfshell/server.go @@ -31,6 +31,7 @@ type Server struct { *http.Server Routes []*Route Logger *Logger + Config *ServerConfig } func NewServerWithConfigAndRoutes(config *ServerConfig, routes []*Route) *Server { @@ -40,7 +41,7 @@ func NewServerWithConfigAndRoutes(config *ServerConfig, routes []*Route) *Server WriteTimeout: time.Duration(config.WriteTimeout) * time.Second, MaxHeaderBytes: 1 << 20, } - server := &Server{httpServer, routes, NewLogger("server")} + server := &Server{httpServer, routes, NewLogger("server"), config} httpServer.Handler = server return server } @@ -64,7 +65,9 @@ func (s *Server) ImageRequestHandler(w *HalfshellResponseWriter, r *HalfshellReq return } - defer func() { go r.Route.Statter.RegisterRequest(w, r) }() + if !s.Config.StatsdDisabled { + defer func() { go r.Route.Statter.RegisterRequest(w, r) }() + } s.Logger.Info("Handling request for image %s with dimensions %v", r.SourceOptions.Path, r.ProcessorOptions.Dimensions) diff --git a/halfshell/source_filesystem.go b/halfshell/source_filesystem.go index c77411f..04403b2 100644 --- a/halfshell/source_filesystem.go +++ b/halfshell/source_filesystem.go @@ -48,7 +48,7 @@ func NewFileSystemImageSourceWithConfig(config *SourceConfig) ImageSource { baseDirectory, err := os.Open(source.Config.Directory) if os.IsNotExist(err) { - source.Logger.Info(source.Config.Directory, " does not exit. Creating.") + source.Logger.Info("%s does not exit. Creating.", source.Config.Directory) _ = os.MkdirAll(source.Config.Directory, 0700) baseDirectory, err = os.Open(source.Config.Directory) } @@ -59,7 +59,7 @@ func NewFileSystemImageSourceWithConfig(config *SourceConfig) ImageSource { fileInfo, err := baseDirectory.Stat() if err != nil || !fileInfo.IsDir() { - source.Logger.Fatal("Directory ", source.Config.Directory, " not a directory", err) + source.Logger.Fatal("Directory %s not a directory", source.Config.Directory, err) } return source