Skip to content

Allow loading material colors from texture - #3403

Merged
IntegratedQuantum merged 2 commits into
PixelGuys:masterfrom
yel0h:texture-instead-of-hex
Aug 1, 2026
Merged

Allow loading material colors from texture#3403
IntegratedQuantum merged 2 commits into
PixelGuys:masterfrom
yel0h:texture-instead-of-hex

Conversation

@yel0h

@yel0h yel0h commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

As suggested in #3366 (review), this allows materials to source their colorPalette, outlineColorLight and outlineColorShadow from a small PNG instead of hardcoded ARGB hex literals in the .zig.zon file.

Adds a new optional zon field, .colorTexture = "name.png", a sibling of the existing .texture field. It's resolved through the exact same items/textures/<mod>/ + assets/<mod>/items/textures/ fallback convention already used for item textures. I've defined the format as an N x 2 image, where the top row is the gradient palette, dark to light, and the bottom row's two pixels are outlineColorShadow (x=0) and outlineColorLight (x=1). A fully transparent pixel there means the color is not specified, falling back to the existing computed darken() default. For backward compatibility, it uses the old hex fields if .colorTexture is absent or fails to load.

image

I've migrated iron_ingot as an example from its hex values and dropped the old colors and outlineColor* fields from iron_ingot.zig.zon in favor of .colorTexture = "iron_ingot_palette.png". I can do so for all the other materials via a script once the specifics of this solution are accepted.

Also includes a small fix in graphics.zig: Image.readFromFile's .asIs orientation option wasn't actually resetting the vertical-flip flag, so once one image was flipped, all images loaded after would also get flipped, regardless of the selected orientation. It now resets the flag.

@Wunka Wunka moved this to High Priority in PRs to review Jul 25, 2026
Comment thread src/graphics.zig

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make a materials folder, so it doesn't clutter with the other item textures.
Though I guess @careeoki should probably make the decision here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please, a separate folder.

Comment thread src/items.zig Outdated
} else {
self.outlineColorLight = darken(self.colorPalette[self.colorPalette.len - 1], 0.7);
}
if (zon.get(u32, "outlineColorShadow")) |colorInt| {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these again, you removed their single use-case already, all new development should use the texture, eventually the other colors attribute can also be removed.

Comment thread src/items.zig Outdated
}

pub fn init(self: *Material, allocator: NeverFailingAllocator, zon: ZonElement) void {
fn loadColorsFromTexture(self: *Material, allocator: NeverFailingAllocator, colorTexturePath: []const u8, colorReplacementTexturePath: []const u8) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use bools in function arguments/return values. An enum is more readable.

Comment thread src/items.zig Outdated
};
defer image.deinit(main.stackAllocator);
if (image.width == 0 or image.height < 2) {
std.log.err("Material color texture '{s}' must be at least 1x2 pixels (got {}x{}).", .{colorTexturePath, image.width, image.height});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't it need to be at least 2×2 so you have the two outline colors?

Comment thread src/items.zig Outdated
self.colorPalette[x] = image.getRGB(x, 0);
}
const shadow = image.getRGB(0, 1);
self.outlineColorShadow = if (shadow.a != 0) shadow else darken(self.colorPalette[0], 0.5);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of automatic behavior, I think it is often better to output an error.
Furthermore this should reject any alpha less than 100%.

@IntegratedQuantum IntegratedQuantum moved this from High Priority to In review in PRs to review Jul 30, 2026
@yel0h

yel0h commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Ok, now the color textures are loaded from assets/<mod>/materials/, and the outline color pixels are no longer optional.

@IntegratedQuantum

Copy link
Copy Markdown
Member

@careeoki are you otherwise happy with how the image is structured?

@careeoki

Copy link
Copy Markdown
Contributor

Yes, it looks good.

@IntegratedQuantum IntegratedQuantum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, I think this is going to make adding new or tweaking existing materials a lot easier.

I can do so for all the other materials via a script once the specifics of this solution are accepted.

I have also made an issue for this #3451 to also track how to deal with the current fallback logic once that's done.

@IntegratedQuantum
IntegratedQuantum merged commit 8d0386c into PixelGuys:master Aug 1, 2026
1 check passed
@yel0h
yel0h deleted the texture-instead-of-hex branch August 1, 2026 11:50
iNiKKo pushed a commit to iNiKKo/Cubyz that referenced this pull request Aug 1, 2026
As suggested in
PixelGuys#3366 (review),
this allows materials to source their `colorPalette`,
`outlineColorLight` and `outlineColorShadow` from a small PNG instead of
hardcoded ARGB hex literals in the `.zig.zon` file.

Adds a new optional zon field, `.colorTexture = "name.png"`, a sibling
of the existing `.texture` field. It's resolved through the exact same
`items/textures/<mod>/` + `assets/<mod>/items/textures/` fallback
convention already used for item textures. I've defined the format as an
N x 2 image, where the top row is the gradient palette, dark to light,
and the bottom row's two pixels are `outlineColorShadow` (x=0) and
`outlineColorLight` (x=1). A fully transparent pixel there means the
color is not specified, falling back to the existing computed `darken()`
default. For backward compatibility, it uses the old hex fields if
`.colorTexture` is absent or fails to load.

<img width="160" height="64" alt="image"
src="https://github.com/user-attachments/assets/db58fa08-d40b-4e46-ac25-3e796f53733a"
/>

I've migrated `iron_ingot` as an example from its hex values and dropped
the old `colors` and `outlineColor*` fields from `iron_ingot.zig.zon` in
favor of `.colorTexture = "iron_ingot_palette.png"`. I can do so for all
the other materials via a script once the specifics of this solution are
accepted.

Also includes a small fix in `graphics.zig`: `Image.readFromFile`'s
`.asIs` orientation option wasn't actually resetting the vertical-flip
flag, so once one image was flipped, all images loaded after would also
get flipped, regardless of the selected orientation. It now resets the
flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants