Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions examples/filesystem_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"server": {
"port": 8080,
"read_timeout": 5,
"write_timeout": 30
"write_timeout": 30,
"disable_statsd": true
},
"sources": {
"default": {
Expand All @@ -22,11 +23,12 @@
"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
"default_image_width": 120,
"grayscale": true
}
},
"routes": {
Expand Down
35 changes: 28 additions & 7 deletions halfshell/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -73,6 +74,8 @@ type ProcessorConfig struct {
MaxImageHeight uint64
MaxImageWidth uint64
MaxBlurRadiusPercentage float64
GrayscaleByDefault bool
GrayscaleDisabled bool
}

// Parses a JSON configuration file and returns a pointer to a new Config object.
Expand Down Expand Up @@ -149,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"),
}
}

Expand All @@ -167,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),
Expand All @@ -177,6 +181,10 @@ func (c *configParser) parseProcessorConfig(processorName string) *ProcessorConf
MaxImageWidth: c.uintForKeypath("processors.%s.max_image_width", processorName),
MaxBlurRadiusPercentage: c.floatForKeypath("processors.%s.max_blur_radius_percentage", 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{} {
Expand Down Expand Up @@ -226,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
}
19 changes: 18 additions & 1 deletion halfshell/image_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ImageProcessor interface {
type ImageProcessorOptions struct {
Dimensions ImageDimensions
BlurRadius float64
GrayScale bool
}

type imageProcessor struct {
Expand Down Expand Up @@ -73,7 +74,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()
Expand Down Expand Up @@ -138,6 +145,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.GrayscaleDisabled && (ip.Config.GrayscaleByDefault || request.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 {
Expand Down
2 changes: 2 additions & 0 deletions halfshell/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
7 changes: 5 additions & 2 deletions halfshell/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Server struct {
*http.Server
Routes []*Route
Logger *Logger
Config *ServerConfig
}

func NewServerWithConfigAndRoutes(config *ServerConfig, routes []*Route) *Server {
Expand All @@ -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
}
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions halfshell/source_filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down