-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathoptions.go
More file actions
220 lines (197 loc) · 7.14 KB
/
options.go
File metadata and controls
220 lines (197 loc) · 7.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 WoozyMasta
// Source: github.com/woozymasta/bcn
package bcn
const (
// QualityLevelFast prioritizes speed over quality.
QualityLevelFast = 1
// QualityLevelBalanced is the default, balancing speed and quality.
QualityLevelBalanced = 6
// QualityLevelBest prioritizes quality and can be slower.
QualityLevelBest = 8
qualityLevelMin = 1
qualityLevelMax = 10
)
// RGBWeights are used when choosing DXT1 palette indices (and in refinement).
// R, G, B are relative weights; they are normalized when used.
// Used to preserve channels that matter (e.g. blue in normal maps).
type RGBWeights struct {
R, G, B float64
}
// Presets for RGBWeights when encoding DXT1/DXT5 RGB block.
var (
// DefaultRGBWeights is luminance-oriented (green dominant). Use for typical photos/UI.
DefaultRGBWeights = RGBWeights{R: 0.3, G: 0.6, B: 0.1}
// BalancedRGBWeights treats R, G, B equally. Use when all channels matter (e.g. normal maps).
BalancedRGBWeights = RGBWeights{R: 1.0 / 3.0, G: 1.0 / 3.0, B: 1.0 / 3.0}
)
// EncodeOptions configures block encoding and mipmap generation.
type EncodeOptions struct {
// RGBWeights overrides weights for DXT1 palette index selection (R, G, B). Nil = default;
// for DXT5, if nil and block has constant R (e.g. nohq), Balanced is used automatically.
RGBWeights *RGBWeights
// Refinement overrides quality behavior when non-nil (applied on top of QualityLevel).
Refinement *RefinementOptions
// qualitySettings is an internal cache of quality settings derived from QualityLevel and Refinement.
qualitySettings *qualitySettings
// QualityLevel provides a 1..10 quality scale. 0 = default (Balanced).
// Recommended: 1=fast, 6=balanced, 8=best, 9-10=extreme.
QualityLevel int
// Workers controls parallel block encoding. 0 = auto (GOMAXPROCS), 1 = disable parallelism,
// N > 1 = use N workers. Defaults to 0.
Workers int
// GenerateMipmaps enables mipmap generation from the input image.
GenerateMipmaps bool
// UseSRGB enables sRGB-aware downscale for mip generation.
UseSRGB bool
// AlphaThreshold controls DXT1 1-bit alpha cutout (0..255). Default 128.
AlphaThreshold uint8
}
// DecodeOptions configures block decoding.
type DecodeOptions struct {
// Workers controls parallel block decoding. 0 = auto (GOMAXPROCS), 1 = disable parallelism,
// N > 1 = use N workers. Defaults to 0 (auto) when options are omitted.
Workers int
}
// RefinementOptions allows overriding quality behavior derived from QualityLevel.
// Nil fields mean "use defaults".
type RefinementOptions struct {
UsePCA *bool // UsePCA overrides quality behavior derived from QualityLevel.
ColorTries *int // ColorTries overrides quality behavior derived from QualityLevel.
AlphaTries *int // AlphaTries overrides quality behavior derived from QualityLevel.
ColorStep *int // ColorStep overrides quality behavior derived from QualityLevel.
}
// normalizeEncodeOptions applies defaults, bounds and cached derived settings.
func normalizeEncodeOptions(opts *EncodeOptions) EncodeOptions {
if opts == nil {
out := EncodeOptions{QualityLevel: QualityLevelBalanced, AlphaThreshold: 128}
qs := resolveQualitySettings(out)
out.qualitySettings = &qs
return out
}
out := *opts
if out.AlphaThreshold == 0 {
out.AlphaThreshold = 128
}
if out.QualityLevel == 0 {
out.QualityLevel = QualityLevelBalanced
}
if out.QualityLevel < qualityLevelMin {
out.QualityLevel = qualityLevelMin
}
if out.QualityLevel > qualityLevelMax {
out.QualityLevel = qualityLevelMax
}
if out.Refinement != nil {
ref := *out.Refinement
out.Refinement = &ref
normalizeRefinement(out.Refinement)
}
qs := resolveQualitySettings(out)
out.qualitySettings = &qs
return out
}
// qualitySettings stores resolved low-level encoder tuning knobs.
type qualitySettings struct {
usePCA bool
colorTries int
colorStep int
alphaTries int
}
// qualitySettingsForOpts returns cached settings when available, otherwise resolves them.
func qualitySettingsForOpts(opts EncodeOptions) qualitySettings {
if opts.qualitySettings != nil {
return *opts.qualitySettings
}
return resolveQualitySettings(opts)
}
// resolveQualitySettings combines QualityLevel and optional Refinement overrides.
func resolveQualitySettings(opts EncodeOptions) qualitySettings {
var settings qualitySettings
if opts.QualityLevel == 0 {
settings = qualitySettingsFromLevel(QualityLevelBalanced)
} else {
settings = qualitySettingsFromLevel(opts.QualityLevel)
}
if settings.colorStep < 1 {
settings.colorStep = 1
}
if settings.alphaTries == 0 {
settings.alphaTries = settings.colorTries
}
if opts.Refinement != nil {
ref := opts.Refinement
if ref.UsePCA != nil {
settings.usePCA = *ref.UsePCA
}
if ref.ColorTries != nil {
settings.colorTries = clampNonNegative(*ref.ColorTries)
}
if ref.AlphaTries != nil {
settings.alphaTries = clampNonNegative(*ref.AlphaTries)
}
if ref.ColorStep != nil {
step := max(*ref.ColorStep, 1)
settings.colorStep = step
}
}
return settings
}
// qualitySettingsFromLevel maps a 1..10 quality level to concrete search settings.
func qualitySettingsFromLevel(level int) qualitySettings {
switch level {
case 1:
return qualitySettings{usePCA: false, colorTries: 0, colorStep: 1, alphaTries: 0}
case 2:
return qualitySettings{usePCA: false, colorTries: 8, colorStep: 1, alphaTries: 8}
case 3:
return qualitySettings{usePCA: false, colorTries: 16, colorStep: 1, alphaTries: 16}
case 4:
return qualitySettings{usePCA: false, colorTries: 32, colorStep: 1, alphaTries: 32}
case 5:
return qualitySettings{usePCA: true, colorTries: 32, colorStep: 1, alphaTries: 32}
case 6:
return qualitySettings{usePCA: true, colorTries: 64, colorStep: 1, alphaTries: 64}
case 7:
return qualitySettings{usePCA: true, colorTries: 96, colorStep: 1, alphaTries: 96}
case 8:
return qualitySettings{usePCA: true, colorTries: 256, colorStep: 2, alphaTries: 256}
case 9:
return qualitySettings{usePCA: true, colorTries: 384, colorStep: 1, alphaTries: 384}
case 10:
return qualitySettings{usePCA: true, colorTries: 512, colorStep: 1, alphaTries: 512}
default:
return qualitySettingsFromLevel(QualityLevelBalanced)
}
}
// normalizeRefinement clamps refinement override fields to valid ranges in place.
func normalizeRefinement(ref *RefinementOptions) {
if ref.ColorTries != nil {
*ref.ColorTries = clampNonNegative(*ref.ColorTries)
}
if ref.AlphaTries != nil {
*ref.AlphaTries = clampNonNegative(*ref.AlphaTries)
}
if ref.ColorStep != nil && *ref.ColorStep < 1 {
*ref.ColorStep = 1
}
}
// clampNonNegative clamps integer values below zero to zero.
func clampNonNegative(v int) int {
if v < 0 {
return 0
}
return v
}
// getRGBWeights returns (rw, gw, bw) for index selection. If opts.RGBWeights is set, uses it;
// else when blockConstantR (e.g. DXT5 nohq with R=0) returns Balanced; else Default.
func getRGBWeights(opts *EncodeOptions, blockConstantR bool) (rw, gw, bw float64) {
if opts != nil && opts.RGBWeights != nil {
w := opts.RGBWeights
return w.R, w.G, w.B
}
if blockConstantR {
return BalancedRGBWeights.R, BalancedRGBWeights.G, BalancedRGBWeights.B
}
return DefaultRGBWeights.R, DefaultRGBWeights.G, DefaultRGBWeights.B
}