mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-28 22:19:59 +02:00
committed by
Bjørn Erik Pedersen
parent
0672b5c766
commit
21d9057dbf
71
resources/images/dither.go
Normal file
71
resources/images/dither.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// 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 images
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"github.com/disintegration/gift"
|
||||
"github.com/makeworld-the-better-one/dither/v2"
|
||||
)
|
||||
|
||||
var _ gift.Filter = (*ditherFilter)(nil)
|
||||
|
||||
type ditherFilter struct {
|
||||
ditherer *dither.Ditherer
|
||||
}
|
||||
|
||||
var ditherMethodsErrorDiffusion = map[string]dither.ErrorDiffusionMatrix{
|
||||
"atkinson": dither.Atkinson,
|
||||
"burkes": dither.Burkes,
|
||||
"falsefloydsteinberg": dither.FalseFloydSteinberg,
|
||||
"floydsteinberg": dither.FloydSteinberg,
|
||||
"jarvisjudiceninke": dither.JarvisJudiceNinke,
|
||||
"sierra": dither.Sierra,
|
||||
"sierra2": dither.Sierra2,
|
||||
"sierra2_4a": dither.Sierra2_4A,
|
||||
"sierra3": dither.Sierra3,
|
||||
"sierralite": dither.SierraLite,
|
||||
"simple2d": dither.Simple2D,
|
||||
"stevenpigeon": dither.StevenPigeon,
|
||||
"stucki": dither.Stucki,
|
||||
"tworowsierra": dither.TwoRowSierra,
|
||||
}
|
||||
|
||||
var ditherMethodsOrdered = map[string]dither.OrderedDitherMatrix{
|
||||
"clustereddot4x4": dither.ClusteredDot4x4,
|
||||
"clustereddot6x6": dither.ClusteredDot6x6,
|
||||
"clustereddot6x6_2": dither.ClusteredDot6x6_2,
|
||||
"clustereddot6x6_3": dither.ClusteredDot6x6_3,
|
||||
"clustereddot8x8": dither.ClusteredDot8x8,
|
||||
"clustereddotdiagonal16x16": dither.ClusteredDotDiagonal16x16,
|
||||
"clustereddotdiagonal6x6": dither.ClusteredDotDiagonal6x6,
|
||||
"clustereddotdiagonal8x8": dither.ClusteredDotDiagonal8x8,
|
||||
"clustereddotdiagonal8x8_2": dither.ClusteredDotDiagonal8x8_2,
|
||||
"clustereddotdiagonal8x8_3": dither.ClusteredDotDiagonal8x8_3,
|
||||
"clustereddothorizontalline": dither.ClusteredDotHorizontalLine,
|
||||
"clustereddotspiral5x5": dither.ClusteredDotSpiral5x5,
|
||||
"clustereddotverticalline": dither.ClusteredDotVerticalLine,
|
||||
"horizontal3x5": dither.Horizontal3x5,
|
||||
"vertical5x3": dither.Vertical5x3,
|
||||
}
|
||||
|
||||
func (f ditherFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
||||
gift.New().Draw(dst, f.ditherer.Dither(src))
|
||||
}
|
||||
|
||||
func (f ditherFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
|
||||
return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
|
||||
}
|
@@ -17,10 +17,13 @@ package images
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/makeworld-the-better-one/dither/v2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/disintegration/gift"
|
||||
"github.com/spf13/cast"
|
||||
@@ -174,6 +177,56 @@ func (*Filters) Padding(args ...any) gift.Filter {
|
||||
}
|
||||
}
|
||||
|
||||
// Dither creates a filter that dithers an image.
|
||||
func (*Filters) Dither(options ...any) gift.Filter {
|
||||
ditherOptions := struct {
|
||||
Colors []string
|
||||
Method string
|
||||
Serpentine bool
|
||||
Strength float32
|
||||
}{
|
||||
Colors: []string{"000000ff", "ffffffff"},
|
||||
Method: "floydsteinberg",
|
||||
Serpentine: true,
|
||||
Strength: 1.0,
|
||||
}
|
||||
|
||||
if len(options) != 0 {
|
||||
err := mapstructure.WeakDecode(options[0], &ditherOptions)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to decode options: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(ditherOptions.Colors) < 2 {
|
||||
panic("palette must have at least two colors")
|
||||
}
|
||||
|
||||
var palette []color.Color
|
||||
for _, c := range ditherOptions.Colors {
|
||||
cc, err := hexStringToColor(c)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("%q is an invalid color: specify RGB or RGBA using hexadecimal notation", c))
|
||||
}
|
||||
palette = append(palette, cc)
|
||||
}
|
||||
|
||||
d := dither.NewDitherer(palette)
|
||||
if method, ok := ditherMethodsErrorDiffusion[strings.ToLower(ditherOptions.Method)]; ok {
|
||||
d.Matrix = dither.ErrorDiffusionStrength(method, ditherOptions.Strength)
|
||||
d.Serpentine = ditherOptions.Serpentine
|
||||
} else if method, ok := ditherMethodsOrdered[strings.ToLower(ditherOptions.Method)]; ok {
|
||||
d.Mapper = dither.PixelMapperFromMatrix(method, ditherOptions.Strength)
|
||||
} else {
|
||||
panic(fmt.Sprintf("%q is an invalid dithering method: see documentation", ditherOptions.Method))
|
||||
}
|
||||
|
||||
return filter{
|
||||
Options: newFilterOpts(ditherOptions),
|
||||
Filter: ditherFilter{ditherer: d},
|
||||
}
|
||||
}
|
||||
|
||||
// AutoOrient creates a filter that rotates and flips an image as needed per
|
||||
// its EXIF orientation tag.
|
||||
func (*Filters) AutoOrient() gift.Filter {
|
||||
|
Reference in New Issue
Block a user