diff options
author | Joe Mooring <[email protected]> | 2024-02-07 15:42:27 -0800 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2024-02-11 22:51:11 +0200 |
commit | 21d9057dbfe64f668d5fc6a7f458e0984fbf7e56 (patch) | |
tree | f2908e8ee594eb908375f11dd85edc595be8aeb7 /docs | |
parent | 0672b5c76605132475ff18b8c526f1cf0d6affc3 (diff) | |
download | hugo-21d9057dbfe64f668d5fc6a7f458e0984fbf7e56.tar.gz hugo-21d9057dbfe64f668d5fc6a7f458e0984fbf7e56.zip |
Add images.Dither filter
Closes #8598
Diffstat (limited to 'docs')
-rw-r--r-- | docs/content/en/functions/images/Dither.md | 160 | ||||
-rw-r--r-- | docs/layouts/shortcodes/img.html | 381 |
2 files changed, 541 insertions, 0 deletions
diff --git a/docs/content/en/functions/images/Dither.md b/docs/content/en/functions/images/Dither.md new file mode 100644 index 000000000..8084d83c1 --- /dev/null +++ b/docs/content/en/functions/images/Dither.md @@ -0,0 +1,160 @@ +--- +title: images.Dither +description: Returns an image filter that dithers an image. +categories: [] +keywords: [] +action: + aliases: [] + related: + - functions/images/Filter + - functions/images/Process + - methods/resource/Colors + - methods/resource/Filter + returnType: images.filter + signatures: ['images.Dither [OPTIONS]'] +toc: true +--- + +## Options + +colors +: (`string array`) A slice of two or more colors that make up the dithering palette, each expressed as an RGB or RGBA [hexadecimal] value, with or without a leading hash mark. The default values are opaque black (`000000ff`) and opaque white (`ffffffff`). + +[hexadecimal]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color + +method +: (`string`) The dithering method. See the [dithering methods](#dithering-methods) section below for a list of the available methods. Default is `FloydSteinberg`. + +serpentine +: (`bool`) Applicable to error diffusion dithering methods, serpentine controls whether the error diffusion matrix is applied in a serpentine manner, meaning that it goes right-to-left every other line. This greatly reduces line-type artifacts. Default is `true`. + +strength +: (`float`) The strength at which to apply the dithering matrix, typically a value in the range [0, 1]. A value of `1.0` applies the dithering matrix at 100% strength (no modifification of the dither matrix). The `strength` is inversely proportional to contrast; reducing the strength increases the contrast. Setting `strength` to a value such as `0.8` can be useful to reduce noise in the dithered image. Default is `1.0`. + +## Usage + +Create the options map: + +```go-html-template +{{ $opts := dict + "colors" (slice "222222" "808080" "dddddd") + "method" "ClusteredDot4x4" + "strength" 0.85 +}} +``` + +Create the filter: + +```go-html-template +{{ $filter := images.Dither $opts }} +``` + +Or create the filter using the default settings: + +```go-html-template +{{ $filter := images.Dither }} +``` + +{{% include "functions/images/_common/apply-image-filter.md" %}} + +## Dithering methods + +See the [Go documentation] for descriptions of each of the dithering methods below. + +[Go documentation]: https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#pkg-variables + +Error diffusion dithering methods: + +- Atkinson +- Burkes +- FalseFloydSteinberg +- FloydSteinberg +- JarvisJudiceNinke +- Sierra +- Sierra2 +- Sierra2_4A +- Sierra3 +- SierraLite +- Simple2D +- StevenPigeon +- Stucki +- TwoRowSierra + +Ordered dithering methods: + +- ClusteredDot4x4 +- ClusteredDot6x6 +- ClusteredDot6x6_2 +- ClusteredDot6x6_3 +- ClusteredDot8x8 +- ClusteredDotDiagonal16x16 +- ClusteredDotDiagonal6x6 +- ClusteredDotDiagonal8x8 +- ClusteredDotDiagonal8x8_2 +- ClusteredDotDiagonal8x8_3 +- ClusteredDotHorizontalLine +- ClusteredDotSpiral5x5 +- ClusteredDotVerticalLine +- Horizontal3x5 +- Vertical5x3 + +## Example + +This example uses the default dithering options. + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Dither" + filterArgs="" + example=true +>}} + +## Recommendations + +Regardless of dithering method, do both of the following to obtain the best results: + +1. Scale the image _before_ dithering +2. Output the image to a lossless format such as GIF or PNG + +The example below does both of these, and it sets the dithering palette to the three most dominant colors in the image. + + +```go-html-template +{{ with resources.Get "original.jpg" }} + {{ $opts := dict + "method" "ClusteredDotSpiral5x5" + "colors" (first 3 .Colors) + }} + {{ $filters := slice + (images.Process "resize 800x") + (images.Dither $opts) + (images.Process "png") + }} + {{ with . | images.Filter $filters }} + <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt=""> + {{ end }} +{{ end }} +``` + +For best results, if the dithering palette is grayscale, convert the image to grayscale before dithering. + +```go-html-template +{{ $opts := dict "colors" (slice "222" "808080" "ddd") }} +{{ $filters := slice + (images.Process "resize 800x") + (images.Grayscale) + (images.Dither $opts) + (images.Process "png") +}} +{{ with images.Filter $filters . }} + <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt=""> +{{ end }} +``` + +The example above: + +1. Resizes the image to be 800 px wide +2. Converts the image to grayscale +3. Dithers the image using the default (`FloydSteinberg`) dithering method with a grayscale palette +4. Converts the image to the PNG format diff --git a/docs/layouts/shortcodes/img.html b/docs/layouts/shortcodes/img.html new file mode 100644 index 000000000..34476b3d8 --- /dev/null +++ b/docs/layouts/shortcodes/img.html @@ -0,0 +1,381 @@ +{{- /* +Renders the given image using the given filter, if any. + +@param {string} src The path to the image which must be a remote, page, or global resource. +@param {string} [filter] The filter to apply to the image (case-insensitive). +@param {string} [filterArgs] A comma-delimited list of arguments to pass to the filter. +@param {bool} [example=false] If true, renders a before/after example. +@param {int} [exampleWidth=384] Image width, in pixels, when rendering a before/after example. + +@returns {template.HTML} + +@examples + + {{< img src="zion-national-park.jpg" >}} + + {{< img src="zion-national-park.jpg" alt="Zion National Park" >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="process" + filterArgs="resize 400x webp" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="colorize" + filterArgs="180,50,20" + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + >}} + + {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + exampleWidth=400 + >}} + + When using the text filter, provide the arguments in this order: + + 0. The text + 1. The horizontal offset, in pixels, relative to the left of the image (default 20) + 2. The vertical offset, in pixels, relative to the top of the image (default 20) + 3. The font size in pixels (default 64) + 4. The line height (default 1.2) + 5. The font color (default #ffffff) + + {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Text" + filterArgs="Zion National Park,25,250,56" + example=true + >}} + + When using the padding filter, provide all arguments in this order: + + 0. Padding top + 1. Padding right + 2. Padding bottom + 3. Padding right + 4. Canvas color + + {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Padding" + filterArgs="20,50,20,50,#0705" + example=true + >}} + +*/}} + +{{- /* Initialize. */}} +{{- $alt := "" }} +{{- $src := "" }} +{{- $filter := "" }} +{{- $filterArgs := slice }} +{{- $example := false }} +{{- $exampleWidth := 384 }} + +{{- /* Default values to use with the text filter. */}} +{{ $textFilterOpts := dict + "xOffset" 20 + "yOffset" 20 + "fontSize" 64 + "lineHeight" 1.2 + "fontColor" "#ffffff" + "fontPath" "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Regular.ttf" +}} + +{{- /* Get and validate parameters. */}} +{{- with .Get "alt" }} + {{- $alt = .}} +{{- end }} + +{{- with .Get "src" }} + {{- $src = . }} +{{- else }} + {{- errorf "The %q shortcode requires a file parameter. See %s" .Name .Position }} +{{- end }} + +{{- with .Get "filter" }} + {{- $filter = . | lower }} +{{- end }} + +{{- $validFilters := slice + "autoorient" "brightness" "colorbalance" "colorize" "contrast" "dither" + "gamma" "gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay" + "padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text" + "unsharpmask" +}} + +{{- with $filter }} + {{- if not (in $validFilters .) }} + {{- errorf "The filter passed to the %q shortcode is invalid. The filter must be one of %s. See %s" $.Name (delimit $validFilters ", " ", or ") $.Position }} + {{- end }} +{{- end }} + +{{- with .Get "filterArgs" }} + {{- $filterArgs = split . "," }} + {{- $filterArgs = apply $filterArgs "trim" "." " " }} +{{- end }} + +{{- if in (slice "false" false 0) (.Get "example") }} + {{- $example = false }} +{{- else if in (slice "true" true 1) (.Get "example")}} + {{- $example = true }} +{{- end }} + +{{- with .Get "exampleWidth" }} + {{- $exampleWidth = . | int }} +{{- end }} + +{{- /* Get image. */}} +{{- $ctx := dict "page" .Page "src" $src "name" .Name "position" .Position }} +{{- $i := partial "inline/get-resource.html" $ctx }} + +{{- /* Resize if rendering before/after examples. */}} +{{- if $example }} + {{- $i = $i.Resize (printf "%dx" $exampleWidth) }} +{{- end }} + +{{- /* Create filter. */}} +{{- $f := "" }} +{{- $ctx := dict "filter" $filter "args" $filterArgs "name" .Name "position" .Position }} +{{- if eq $filter "autoorient" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.AutoOrient }} +{{- else if eq $filter "brightness" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Brightness (index $filterArgs 0) }} +{{- else if eq $filter "colorbalance" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage red" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage green" "argValue" (index $filterArgs 1) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage blue" "argValue" (index $filterArgs 2) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.ColorBalance (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "colorize" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "hue" "argValue" (index $filterArgs 0) "min" 0 "max" 360) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "saturation" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 2) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Colorize (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "contrast" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Contrast (index $filterArgs 0) }} +{{- else if eq $filter "dither" }} + {{- $f = images.Dither }} +{{- else if eq $filter "gamma" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "gamma" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Gamma (index $filterArgs 0) }} +{{- else if eq $filter "gaussianblur" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.GaussianBlur (index $filterArgs 0) }} +{{- else if eq $filter "grayscale" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Grayscale }} +{{- else if eq $filter "hue" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "shift" "argValue" (index $filterArgs 0) "min" -180 "max" 180) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Hue (index $filterArgs 0) }} +{{- else if eq $filter "invert" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Invert }} +{{- else if eq $filter "opacity" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "opacity" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Opacity (index $filterArgs 0) }} +{{- else if eq $filter "overlay" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }} + {{- $overlayImg := partial "inline/get-resource.html" $ctx }} + {{- $f = images.Overlay $overlayImg (index $filterArgs 1 | float ) (index $filterArgs 2 | float) }} +{{- else if eq $filter "padding" }} + {{- $ctx = merge $ctx (dict "argsRequired" 5) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Padding + (index $filterArgs 0 | int) + (index $filterArgs 1 | int) + (index $filterArgs 2 | int) + (index $filterArgs 3 | int) + (index $filterArgs 4) + }} +{{- else if eq $filter "pixelate" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "size" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Pixelate (index $filterArgs 0) }} +{{- else if eq $filter "process" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Process (index $filterArgs 0) }} +{{- else if eq $filter "saturation" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Saturation (index $filterArgs 0) }} +{{- else if eq $filter "sepia" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sepia (index $filterArgs 0) }} +{{- else if eq $filter "sigmoid" }} + {{- $ctx = merge $ctx (dict "argsRequired" 2) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "midpoint" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "factor" "argValue" (index $filterArgs 1) "min" -10 "max" 10) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sigmoid (index $filterArgs 0) (index $filterArgs 1) }} +{{- else if eq $filter "text" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" $textFilterOpts.fontPath "name" .Name "position" .Position }} + {{- $font := or (partial "inline/get-resource.html" $ctx) }} + {{- $fontSize := or (index $filterArgs 3 | int) $textFilterOpts.fontSize }} + {{- $lineHeight := math.Max (or (index $filterArgs 4 | float) $textFilterOpts.lineHeight) 1 }} + {{- $opts := dict + "x" (or (index $filterArgs 1 | int) $textFilterOpts.xOffset) + "y" (or (index $filterArgs 2 | int) $textFilterOpts.yOffset) + "size" $fontSize + "linespacing" (mul (sub $lineHeight 1) $fontSize) + "color" (or (index $filterArgs 5) $textFilterOpts.fontColor) + "font" $font + }} + {{- $f = images.Text (index $filterArgs 0) $opts }} +{{- else if eq $filter "unsharpmask" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "amount" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "threshold" "argValue" (index $filterArgs 2) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.UnsharpMask (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- end }} + +{{- /* Apply filter. */}} +{{- $fi := $i }} +{{- with $f }} + {{- $fi = $i.Filter . }} +{{- end }} + +{{- /* Render. */}} +{{- if $example }} + <p>Original</p> + <img class='di ba b--black-20' style="width: initial;" src="{{ $i.RelPermalink }}" alt="{{ $alt }}"> + <p>Processed</p> + <img class='di ba b--black-20' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}"> +{{- else -}} + <img class='di' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}"> +{{- end }} + +{{- define "validate-arg-count" }} + {{- $msg := "When using the %q filter, the %q shortcode requires an args parameter with %d %s. See %s" }} + {{- if lt (len .args) .argsRequired }} + {{- $text := "values" }} + {{- if eq 1 .argsRequired }} + {{- $text = "value" }} + {{- end }} + {{- errorf $msg .filter .name .argsRequired $text .position }} + {{- end }} +{{- end }} + +{{- define "validate-arg-value" }} + {{- $msg := "The %q argument passed to the %q shortcode is invalid. Expected a value in the range [%v,%v], but received %v. See %s" }} + {{- if or (lt .argValue .min) (gt .argValue .max) }} + {{- errorf $msg .argName .name .min .max .argValue .position }} + {{- end }} +{{- end }} + +{{- define "partials/inline/get-resource.html" }} + {{- $r := "" }} + {{- $u := urls.Parse .src }} + {{- $msg := "The %q shortcode was unable to resolve %s. See %s" }} + {{- if $u.IsAbs }} + {{- with resources.GetRemote $u.String }} + {{- with .Err }} + {{- errorf "%s" }} + {{- else }} + {{- /* This is a remote resource. */}} + {{- $r = . }} + {{- end }} + {{- else }} + {{- errorf $msg $.name $u.String $.position }} + {{- end }} + {{- else }} + {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} + {{- /* This is a page resource. */}} + {{- $r = . }} + {{- else }} + {{- with resources.Get $u.Path }} + {{- /* This is a global resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg $.name $u.Path $.position }} + {{- end }} + {{- end }} + {{- end }} + {{- return $r}} +{{- end -}} |