Skip to content

Commit fd84732

Browse files
committed
Add alpha gamma & premultiply, refactor masks
Introduce MaskBuffers and add alpha gamma + premultiply support. - Replace std::pair<ImageBuf,ImageBuf> mask API with MaskBuffers (alpha, rgbAlpha, originalAlpha) and propagate through solidify_main and processing. - Implement mask_load/img_load changes to extract, preserve, gamma-correct, and optionally premultiply alpha; add helper functions for extracting alpha, applying gamma, and creating alpha multipliers. - Add new settings: Premultiply (premultiplyAlpha) and AlphaGamma with defaults and config (sldf_config.toml) support; validate/clamp values on load and print them in diagnostics. - UI updates: reorganize Alpha menu (Use Alpha, Premultiply, Gamma control), rename some menu labels, and add compact input layout helper. - Adjust image I/O behavior (UnassociatedAlpha flag change) and handle original alpha buffers when needed. - Update and add unit tests covering mask gamma handling, preserving original alpha, and premultiply behavior; update settings tests to include new fields. This refactor ensures mask and embedded alpha are handled with configurable gamma correction and optional premultiplication, while preserving original alpha data for export or further processing.
1 parent c06a904 commit fd84732

11 files changed

Lines changed: 359 additions & 63 deletions

Solidify/src/IMAGEIO.CPP

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,75 @@ debugImageBufWrite(const ImageBuf& buf, const std::string& filename)
8383
}
8484
}
8585

86-
std::pair<ImageBuf, ImageBuf>
86+
static void
87+
setAlphaBufferSpec(ImageBuf& buf)
88+
{
89+
if (buf.nchannels() > 0) {
90+
buf.specmod().channelnames[0] = "A";
91+
buf.specmod().alpha_channel = 0;
92+
}
93+
}
94+
95+
static bool
96+
extractAlphaChannel(ImageBuf* dst, const ImageBuf& src, int alphaChannel)
97+
{
98+
int channelorder[] = { alphaChannel };
99+
float channelvalues[] = { 1.0f };
100+
*dst = ImageBufAlgo::channels(src, 1, channelorder, channelvalues);
101+
if (!dst->initialized() || dst->has_error()) {
102+
spdlog::error("Error: Could not extract alpha channel");
103+
spdlog::error("{}", dst->geterror());
104+
return false;
105+
}
106+
setAlphaBufferSpec(*dst);
107+
return true;
108+
}
109+
110+
static bool
111+
applyAlphaGamma(ImageBuf& alphaBuf)
112+
{
113+
if (std::abs(settings.alphaGamma - 1.0f) <= 0.000001f) {
114+
return true;
115+
}
116+
117+
float minValue[] = { 0.0f };
118+
float maxValue[] = { 1.0f };
119+
if (!ImageBufAlgo::clamp(alphaBuf, alphaBuf, minValue, maxValue, false)) {
120+
spdlog::error("Error: Could not clamp alpha before gamma");
121+
spdlog::error("{}", alphaBuf.geterror());
122+
return false;
123+
}
124+
125+
const float exponent = 1.0f / std::max(settings.alphaGamma, 0.01f);
126+
float powValue[] = { exponent };
127+
if (!ImageBufAlgo::pow(alphaBuf, alphaBuf, powValue)) {
128+
spdlog::error("Error: Could not apply alpha gamma");
129+
spdlog::error("{}", alphaBuf.geterror());
130+
return false;
131+
}
132+
133+
setAlphaBufferSpec(alphaBuf);
134+
return true;
135+
}
136+
137+
static ImageBuf
138+
makeAlphaMultiplier(const ImageBuf& alphaBuf, int channelCount)
139+
{
140+
if (channelCount == 4) {
141+
int channelorder[] = { 0, 0, 0, -1 };
142+
float channelvalues[] = { 1.0f, 1.0f, 1.0f, 1.0f };
143+
return ImageBufAlgo::channels(alphaBuf, 4, channelorder, channelvalues);
144+
}
145+
146+
int channelorder[] = { 0, -1 };
147+
float channelvalues[] = { 1.0f, 1.0f };
148+
return ImageBufAlgo::channels(alphaBuf, 2, channelorder, channelvalues);
149+
}
150+
151+
MaskBuffers
87152
mask_load(const std::string& mask_file, const SolidifyProgressCallback& progressCallback)
88153
{
154+
MaskBuffers result;
89155
ImageBuf alpha_buf(mask_file);
90156
spdlog::info("Reading {}", mask_file);
91157

@@ -107,8 +173,15 @@ mask_load(const std::string& mask_file, const SolidifyProgressCallback& progress
107173
const int height = alpha_buf.spec().height;
108174
spdlog::info("Mask size: {}x{}", width, height);
109175

110-
alpha_buf.specmod().channelnames[0] = "A";
111-
alpha_buf.specmod().alpha_channel = 0;
176+
setAlphaBufferSpec(alpha_buf);
177+
if (settings.alphaMode == 1) {
178+
result.originalAlpha = alpha_buf.copy(TypeDesc::FLOAT);
179+
setAlphaBufferSpec(result.originalAlpha);
180+
}
181+
182+
if (!applyAlphaGamma(alpha_buf)) {
183+
return {};
184+
}
112185

113186
int channelorder[] = { 0, 0, 0 };
114187
float channelvalues[] = { 1.0f, 1.0f, 1.0f };
@@ -120,14 +193,19 @@ mask_load(const std::string& mask_file, const SolidifyProgressCallback& progress
120193
return {};
121194
}
122195

123-
return { alpha_buf, rgb_alpha };
196+
result.alpha = std::move(alpha_buf);
197+
result.rgbAlpha = std::move(rgb_alpha);
198+
return result;
124199
}
125200

126201
bool
127202
img_load(ImageBuf& outBuf, const std::string& inputFileName, bool external_alpha,
128-
const SolidifyProgressCallback& progressCallback)
203+
ImageBuf* originalAlpha, const SolidifyProgressCallback& progressCallback)
129204
{
130205
int last_channel = -1;
206+
if (originalAlpha != nullptr) {
207+
originalAlpha->clear();
208+
}
131209

132210
ImageSpec& spec = outBuf.specmod();
133211
const int nchannels = spec.nchannels;
@@ -170,24 +248,40 @@ img_load(ImageBuf& outBuf, const std::string& inputFileName, bool external_alpha
170248
}
171249

172250
if (!external_alpha && settings.isSolidify) {
173-
ImageBuf alphs_rgba;
174-
if (nchannels == 4) {
175-
int channelorder[] = { 3, 3, 3 };
176-
float channelvalues[] = { 1.0f, 1.0f, 1.0f };
177-
alphs_rgba = ImageBufAlgo::channels(outBuf, 3, channelorder, channelvalues);
178-
} else if (nchannels == 2) {
179-
int channelorder[] = { 1 };
180-
float channelvalues[] = { 1.0f };
181-
alphs_rgba = ImageBufAlgo::channels(outBuf, 3, channelorder, channelvalues);
182-
}
251+
const int alphaChannel = nchannels == 4 ? 3 : (nchannels == 2 ? 1 : -1);
252+
if (alphaChannel >= 0) {
253+
if (originalAlpha != nullptr && settings.alphaMode == 1
254+
&& !extractAlphaChannel(originalAlpha, outBuf, alphaChannel)) {
255+
return false;
256+
}
183257

184-
if (alphs_rgba.initialized()) {
185-
read_ok = ImageBufAlgo::mul(outBuf, outBuf, alphs_rgba);
186-
if (!read_ok) {
187-
spdlog::error("Error: Could not multiply alpha");
258+
ImageBuf alphaBuf;
259+
if (!extractAlphaChannel(&alphaBuf, outBuf, alphaChannel) || !applyAlphaGamma(alphaBuf)) {
260+
return false;
261+
}
262+
263+
ImageBufAlgo::paste(outBuf, 0, 0, 0, alphaChannel, alphaBuf);
264+
if (outBuf.has_error()) {
265+
spdlog::error("Error: Could not paste gamma-corrected alpha");
188266
spdlog::error("{}", outBuf.geterror());
189267
return false;
190268
}
269+
270+
if (settings.premultiplyAlpha) {
271+
ImageBuf alphaMultiplier = makeAlphaMultiplier(alphaBuf, nchannels);
272+
if (!alphaMultiplier.initialized() || alphaMultiplier.has_error()) {
273+
spdlog::error("Error: Could not expand alpha multiplier");
274+
spdlog::error("{}", alphaMultiplier.geterror());
275+
return false;
276+
}
277+
278+
read_ok = ImageBufAlgo::mul(outBuf, outBuf, alphaMultiplier);
279+
if (!read_ok) {
280+
spdlog::error("Error: Could not multiply alpha");
281+
spdlog::error("{}", outBuf.geterror());
282+
return false;
283+
}
284+
}
191285
}
192286
}
193287

Solidify/src/UI.CPP

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,20 @@ MenuRadioInt(const char* label, int& variable, int value)
9494
}
9595
}
9696

97+
static void
98+
PushCompactMenuInputFrame()
99+
{
100+
const ImGuiStyle& style = ImGui::GetStyle();
101+
const float targetHeight = ImGui::GetFrameHeight() / 1.5f;
102+
const float compactPaddingY = std::max(0.0f, (targetHeight - ImGui::GetFontSize()) * 0.5f);
103+
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, compactPaddingY));
104+
}
105+
97106
static void
98107
SetAlphaMode(uint mode, const char* label)
99108
{
100109
settings.alphaMode = mode;
101-
SetStatus(std::string("Alpha/Mask set to ") + label);
110+
SetStatus(std::string("Mask set to ") + label);
102111

103112
if (mode == 2) {
104113
settings.isSolidify = false;
@@ -410,12 +419,38 @@ AppMenuBar()
410419
settings.isSolidify = !settings.isSolidify;
411420
SetStatus(settings.isSolidify ? "Solidify Enabled" : "Solidify Disabled");
412421
}
413-
if (ImGui::MenuItem("Use Alpha", nullptr, settings.useAlpha)) {
414-
settings.useAlpha = !settings.useAlpha;
415-
SetStatus(settings.useAlpha ? "Use Alpha Enabled" : "Use Alpha Disabled");
422+
423+
if (ImGui::BeginMenu("Alpha")) {
424+
if (ImGui::MenuItem("Use Alpha", nullptr, settings.useAlpha)) {
425+
settings.useAlpha = !settings.useAlpha;
426+
SetStatus(settings.useAlpha ? "Use Alpha Enabled" : "Use Alpha Disabled");
427+
}
428+
if (ImGui::MenuItem("Premultiply", nullptr, settings.premultiplyAlpha)) {
429+
settings.premultiplyAlpha = !settings.premultiplyAlpha;
430+
SetStatus(settings.premultiplyAlpha ? "Premultiply Enabled" : "Premultiply Disabled");
431+
}
432+
ImGui::Separator();
433+
if (ImGui::BeginTable("AlphaForm", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) {
434+
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 68.0f);
435+
ImGui::TableSetupColumn("Control", ImGuiTableColumnFlags_WidthFixed, 120.0f);
436+
ImGui::TableNextRow();
437+
PushCompactMenuInputFrame();
438+
ImGui::TableSetColumnIndex(0);
439+
ImGui::AlignTextToFramePadding();
440+
ImGui::TextUnformatted("Gamma");
441+
ImGui::TableSetColumnIndex(1);
442+
ImGui::SetNextItemWidth(120.0f);
443+
if (ImGui::InputFloat("##AlphaGamma", &settings.alphaGamma, 0.1f, 1.0f, "%.3f")) {
444+
settings.alphaGamma = std::clamp(settings.alphaGamma, 0.01f, 10.0f);
445+
SetStatus("Alpha Gamma Updated");
446+
}
447+
ImGui::PopStyleVar();
448+
ImGui::EndTable();
449+
}
450+
ImGui::EndMenu();
416451
}
417452

418-
if (ImGui::BeginMenu("Alpha/Mask")) {
453+
if (ImGui::BeginMenu("Mask")) {
419454
if (ImGui::MenuItem("Disable", nullptr, settings.alphaMode == 0)) {
420455
SetAlphaMode(0, "Disable");
421456
}
@@ -509,15 +544,23 @@ AppMenuBar()
509544
if (ImGui::MenuItem("Arbitrary", nullptr, settings.grayscaleMode == 7)) {
510545
SetGrayscaleMode(7, "Arbitrary");
511546
}
512-
const ImGuiStyle& style = ImGui::GetStyle();
513-
const float targetFrameHeight = ImGui::GetFrameHeight() / 1.5f;
514-
const float arbitraryPaddingY = std::max(0.0f, (targetFrameHeight - ImGui::GetFontSize()) * 0.5f);
515-
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, arbitraryPaddingY));
516-
ImGui::SetNextItemWidth(220.0f);
517-
if (ImGui::InputFloat3("Weights", settings.grayscaleWeights, "%.4f")) {
518-
settings.grayscaleMode = 7;
547+
if (ImGui::BeginTable("GrayscaleForm", 2,
548+
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) {
549+
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 68.0f);
550+
ImGui::TableSetupColumn("Control", ImGuiTableColumnFlags_WidthFixed, 220.0f);
551+
ImGui::TableNextRow();
552+
PushCompactMenuInputFrame();
553+
ImGui::TableSetColumnIndex(0);
554+
ImGui::AlignTextToFramePadding();
555+
ImGui::TextUnformatted("Weights");
556+
ImGui::TableSetColumnIndex(1);
557+
ImGui::SetNextItemWidth(220.0f);
558+
if (ImGui::InputFloat3("##GrayscaleWeights", settings.grayscaleWeights, "%.4f")) {
559+
settings.grayscaleMode = 7;
560+
}
561+
ImGui::PopStyleVar();
562+
ImGui::EndTable();
519563
}
520-
ImGui::PopStyleVar();
521564
ImGui::EndMenu();
522565
}
523566

Solidify/src/imageio.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ struct OIIOProgressContext {
3636
float scale = 1.0f;
3737
};
3838

39+
struct MaskBuffers {
40+
ImageBuf alpha;
41+
ImageBuf rgbAlpha;
42+
ImageBuf originalAlpha;
43+
};
44+
3945
bool
4046
m_progress_callback(void* opaque_data, float portion_done);
4147

@@ -46,11 +52,11 @@ formatText(TypeDesc format);
4652
void
4753
formatFromBuff(ImageBuf& buf);
4854

49-
std::pair<ImageBuf, ImageBuf>
55+
MaskBuffers
5056
mask_load(const std::string& mask_file, const SolidifyProgressCallback& progressCallback);
5157
bool
5258
img_load(ImageBuf& outBuf, const std::string& inputFileName, bool external_alpha,
53-
const SolidifyProgressCallback& progressCallback);
59+
ImageBuf* originalAlpha, const SolidifyProgressCallback& progressCallback);
5460

5561
void
5662
debugImageBufWrite(const ImageBuf& buf, const std::string& filename);

Solidify/src/processing.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ doProcessing(const std::vector<std::string>& filePaths, SolidifyProgressCallback
191191

192192
std::string mask_file = checkAlpha(fileNames);
193193

194-
std::pair<ImageBuf, ImageBuf> mask_pair;
194+
MaskBuffers maskBuffers;
195195
if (!mask_file.empty()) {
196196
spdlog::info("Mask file: {} will be used.", mask_file);
197-
mask_pair = mask_load(mask_file, progressCallback);
198-
if (!mask_pair.first.initialized() || !mask_pair.second.initialized()) {
197+
maskBuffers = mask_load(mask_file, progressCallback);
198+
if (!maskBuffers.alpha.initialized() || !maskBuffers.rgbAlpha.initialized()) {
199199
spdlog::error("Mask load failed: {}", mask_file);
200200
return false;
201201
}
@@ -265,7 +265,7 @@ doProcessing(const std::vector<std::string>& filePaths, SolidifyProgressCallback
265265
updateProgress(i, p, std::move(status));
266266
};
267267

268-
const bool ok = solidify_main(infile, outfile, mask_pair, fileCallback);
268+
const bool ok = solidify_main(infile, outfile, maskBuffers, fileCallback);
269269
updateProgress(i, 1.0f, ok ? ("Done: " + fileNameOnly(outfile)) : ("Failed: " + fileNameOnly(infile)));
270270
return ok;
271271
}));

Solidify/src/settings.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,13 @@ loadSettings(Settings& outSettings, const std::string& filename)
174174

175175
get_value(data, "Global", "Solidify", loaded.isSolidify);
176176
get_value(data, "Global", "UseAlpha", loaded.useAlpha);
177+
get_value(data, "Global", "Premultiply", loaded.premultiplyAlpha);
177178
get_value(data, "Global", "ExportAlpha", loaded.alphaMode);
178179
get_value(data, "Global", "Console", loaded.conEnable);
179180
get_value(data, "Global", "Threads", loaded.numThreads);
180181
get_value(data, "Global", "QueueLimit", loaded.queueLimit);
181182
get_value(data, "Global", "Verbosity", loaded.verbosity);
183+
get_value(data, "Global", "AlphaGamma", loaded.alphaGamma);
182184

183185
if (data.contains("Global") && data.at("Global").contains("MaskNames")) {
184186
std::vector<std::string> values = toml::find<std::vector<std::string>>(data, "Global", "MaskNames");
@@ -238,6 +240,7 @@ loadSettings(Settings& outSettings, const std::string& filename)
238240
loaded.swapBasis = std::clamp<uint>(loaded.swapBasis, 0, 5);
239241
loaded.swapInvertMask = std::clamp<uint>(loaded.swapInvertMask, 0, 7);
240242
loaded.grayscaleMode = std::clamp<uint>(loaded.grayscaleMode, 0, 7);
243+
loaded.alphaGamma = std::clamp(loaded.alphaGamma, 0.01f, 10.0f);
241244
loaded.defFormat = std::clamp(loaded.defFormat, 0, 8);
242245
loaded.fileFormat = std::clamp(loaded.fileFormat, -1, 8);
243246
loaded.defBDepth = std::clamp(loaded.defBDepth, 0, 6);
@@ -405,9 +408,11 @@ printSettings(Settings& settings)
405408
spdlog::info("--- Current Settings ---");
406409
spdlog::info("Solidify: {}", settings.isSolidify ? "Enabled" : "Disabled");
407410
spdlog::info("Use Alpha: {}", settings.useAlpha ? "Embedded image alpha" : "External mask file");
408-
spdlog::info("Alpha/Mask: {}", settings.alphaMode == 0
409-
? "Remove Alpha"
410-
: (settings.alphaMode == 1 ? "Preserve Alpha" : "Export Alpha only"));
411+
spdlog::info("Premultiply: {}", settings.premultiplyAlpha ? "Enabled" : "Disabled");
412+
spdlog::info("Alpha Gamma: {}", settings.alphaGamma);
413+
spdlog::info("Mask: {}", settings.alphaMode == 0
414+
? "Remove Alpha"
415+
: (settings.alphaMode == 1 ? "Preserve Alpha" : "Export Alpha only"));
411416
spdlog::info("Parallel Threads: {}", settings.numThreads);
412417
spdlog::info("Queue Limit: {}", settings.queueLimit);
413418
spdlog::info("Normalize Mode: {}", settings.normMode);

Solidify/src/settings.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ enum JpegSubsamplingMode : int {
6969
};
7070

7171
struct Settings {
72-
bool isSolidify, useAlpha, conEnable;
72+
bool isSolidify, useAlpha, premultiplyAlpha, conEnable;
7373
uint normMode, rangeMode, repairMode, alphaMode;
7474
uint swapBasis, swapInvertMask, grayscaleMode;
7575
int fileFormat, defFormat;
@@ -78,6 +78,7 @@ struct Settings {
7878
uint numThreads;
7979
uint queueLimit;
8080
uint verbosity;
81+
float alphaGamma;
8182
float grayscaleWeights[3];
8283
int tiffCompression, tiffZipLevel;
8384
int exrCompression, exrZipLevel, exrDwaLevel;
@@ -96,14 +97,16 @@ struct Settings {
9697

9798
void reSettings()
9899
{
99-
conEnable = true;
100-
isSolidify = true;
101-
useAlpha = true;
100+
conEnable = true;
101+
isSolidify = true;
102+
useAlpha = true;
103+
premultiplyAlpha = true;
102104

103105
alphaMode = 0;
104106
numThreads = 3;
105107
queueLimit = 0;
106108
verbosity = 3;
109+
alphaGamma = 1.0f;
107110
normMode = 1;
108111
repairMode = 0;
109112
swapBasis = 0;

0 commit comments

Comments
 (0)