-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proportional scaling for the sprite's texture. #17258
Proportional scaling for the sprite's texture. #17258
Conversation
68bd63f
to
0e7ca6f
Compare
This change introduces new texture scaling options for sprites that maintain aspect ratio: - Add `TextureScale` enum with `FillCenter`, `FillStart`, `FillEnd`, `FitCenter`, `FitStart`, and `FitEnd` modes - Extend `SpriteImageMode` with `ScaleMode` variant to support the new scaling options
Update `Cargo.toml` accordingly.
49186a3
to
296174f
Compare
I like the feature, and the algorithms seem to be correct. Some mild style / docs / naming feedback, but do push back if you disagree with any of them. Much of this feels like it belongs in |
- Update SpriteImageMode::ScaleMode variant to Scale - Fix unreachable error messages to be more specific - Clean up sprite scale example code - Update documentation comments This change improves naming consistency and makes the sprite scaling API more intuitive while maintaining all existing functionality.
Cargo.toml
Outdated
|
||
[package.metadata.example.sprite_scale] | ||
name = "Sprite Scale" | ||
description = "Shows how a sprite could be scaled into a rectangle while keeping the aspect ratio" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description = "Shows how a sprite could be scaled into a rectangle while keeping the aspect ratio" | |
description = "Shows how a sprite can be scaled into a rectangle while keeping the aspect ratio" |
examples/README.md
Outdated
@@ -120,6 +120,7 @@ Example | Description | |||
[Sprite](../examples/2d/sprite.rs) | Renders a sprite | |||
[Sprite Animation](../examples/2d/sprite_animation.rs) | Animates a sprite in response to an event | |||
[Sprite Flipping](../examples/2d/sprite_flipping.rs) | Renders a sprite flipped along an axis | |||
[Sprite Scale](../examples/2d/sprite_scale.rs) | Shows how a sprite could be scaled into a rectangle while keeping the aspect ratio |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Sprite Scale](../examples/2d/sprite_scale.rs) | Shows how a sprite could be scaled into a rectangle while keeping the aspect ratio | |
[Sprite Scale](../examples/2d/sprite_scale.rs) | Shows how a sprite can be scaled into a rectangle while keeping the aspect ratio |
crates/bevy_sprite/src/render/mod.rs
Outdated
ScalingMode::FitStart => { | ||
if texture_ratio > quad_ratio { | ||
// The quad is scaled to match the image ratio, and the quad translation is adjusted | ||
// to start of the quad within the original quad size. | ||
let scale = Vec2::new(1.0, quad_ratio / texture_ratio); | ||
let new_quad = *quad_size * scale; | ||
let offset = *quad_size - new_quad; | ||
*quad_translation = Vec2::new(0.0, -offset.y); | ||
*quad_size = new_quad; | ||
} else { | ||
let scale = Vec2::new(texture_ratio / quad_ratio, 1.0); | ||
let new_quad = *quad_size * scale; | ||
let offset = *quad_size - new_quad; | ||
*quad_translation = Vec2::new(offset.x, 0.0); | ||
*quad_size = new_quad; | ||
} | ||
} | ||
ScalingMode::FitEnd => { | ||
if texture_ratio > quad_ratio { | ||
let scale = Vec2::new(1.0, quad_ratio / texture_ratio); | ||
let new_quad = *quad_size * scale; | ||
let offset = *quad_size - new_quad; | ||
*quad_translation = Vec2::new(0.0, offset.y); | ||
*quad_size = new_quad; | ||
} else { | ||
let scale = Vec2::new(texture_ratio / quad_ratio, 1.0); | ||
let new_quad = *quad_size * scale; | ||
let offset = *quad_size - new_quad; | ||
*quad_translation = Vec2::new(-offset.x, 0.0); | ||
*quad_size = new_quad; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are identical except for 1 minus sign, and there are similar duplication in the other Start/End cases. On one hand it would be nice if the repetition could be factored out so e.g. FitEnd is just FitStart and set the minus. On the other hand this code is pretty simple and easy to read.
It might also become simpler if you match on (scaling_mode, quad_ratio > texture_ratio)
since every case has that if inside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your concern about code duplication, and I agree that maintaining clarity is important. However, considering the minimal amount of code duplication we currently have, I believe it's reasonable to keep things as they are. The existing structure seems to be functioning well without introducing unnecessary complexity.
crates/bevy_sprite/src/sprite.rs
Outdated
/// Fill rect with a centered texture. | ||
#[default] | ||
FillCenter, | ||
/// Scale the texture to fill the rect with a start of the texture, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Scale the texture to fill the rect with a start of the texture, | |
/// Scale the texture to fill the rect with the start of the texture, |
This isn't really very clear about what the "start" of the texture is.
crates/bevy_sprite/src/sprite.rs
Outdated
/// Scale the texture to fill the rect with a start of the texture, | ||
/// maintaining the aspect ratio. | ||
FillStart, | ||
/// Scale the texture to fill the rect with a end of the texture, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Scale the texture to fill the rect with a end of the texture, | |
/// Scale the texture to fill the rect with the end of the texture, |
crates/bevy_sprite/src/sprite.rs
Outdated
FillEnd, | ||
/// Scaling the texture will maintain the original aspect ratio | ||
/// and ensure that the original texture fits entirely inside the rect. | ||
/// At least one axis (X or Y) will fit exactly. The result is centered inside the rect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// At least one axis (X or Y) will fit exactly. The result is centered inside the rect. | |
/// At least one axis (x or y) will fit exactly. The result is centered inside the rect. |
crates/bevy_sprite/src/sprite.rs
Outdated
FitCenter, | ||
/// Scaling the texture will maintain the original aspect ratio | ||
/// and ensure that the original texture fits entirely inside rect. | ||
/// At least one axis (X or Y) will fit exactly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// At least one axis (X or Y) will fit exactly. | |
/// At least one axis (x or y) will fit exactly. |
crates/bevy_sprite/src/sprite.rs
Outdated
FitStart, | ||
/// Scaling the texture will maintain the original aspect ratio | ||
/// and ensure that the original texture fits entirely inside rect. | ||
/// At least one axis (X or Y) will fit exactly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// At least one axis (X or Y) will fit exactly. | |
/// At least one axis (x or y) will fit exactly. |
|
||
let square = asset_server.load("textures/slice_square_2.png"); | ||
let banner = asset_server.load("branding/banner.png"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some example cases with a sprite from a texture atlas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some example cases with a sprite from a texture atlas.
It's important to note that the sprite sheet won't function correctly with scaling. Scaling completely overrides both the scaling and offset for the sprite, disregarding the rect
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just updated the documentation to properly notify a user that it couldn't be used for a sprite sheet. For now, I don't know how to easily add additional scaling of the sprite sheet to the desired rect
@@ -813,7 +813,10 @@ fn compute_texture_slices( | |||
[[0., 0., 1., 1.], [0., 0., 1., 1.], [1., 1., rx, ry]] | |||
} | |||
SpriteImageMode::Auto => { | |||
unreachable!("Slices should not be computed for ImageScaleMode::Stretch") | |||
unreachable!("Slices should not be computed for SpriteImageMode::Stretch") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unreachable!("Slices should not be computed for SpriteImageMode::Stretch") | |
unreachable!("Slices can not be computed for SpriteImageMode::Auto") |
unreachable!("Slices should not be computed for SpriteImageMode::Stretch") | ||
} | ||
SpriteImageMode::Scale(_) => { | ||
unreachable!("Slices should not be computed for SpriteImageMode::Scale") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unreachable!("Slices should not be computed for SpriteImageMode::Scale") | |
unreachable!("Slices can not be computed for SpriteImageMode::Scale") |
@@ -430,6 +431,7 @@ pub fn extract_sprites( | |||
image_handle_id: sprite.image.id(), | |||
anchor: sprite.anchor.as_vec(), | |||
original_entity: Some(original_entity), | |||
scaling_mode: sprite.image_mode.scale(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer it if the sprite geometry was calculated during extraction rather than in prepare.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the geometry logic happens in prepare
for now. I am going with less resistance and fewer changes in the codebase to achieve the necessary functionality.
crates/bevy_sprite/src/sprite.rs
Outdated
pub enum ScalingMode { | ||
/// Scale the texture uniformly (maintain the texture's aspect ratio) | ||
/// so that both dimensions (width and height) of the texture will be equal | ||
/// to or larger than the corresponding dimension of the rect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you mean the rect
field of the sprite? Needs to be clearer I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a good start but looks like it still needs some work. It took me a while to understand the API and the docs aren't very clear. It probably doesn't make much that much impact but I'd like to see some sprite benchmarks as well.
Maybe there needs to be some sort of scale with overflow option as well, but I'm not really sure and that could be added in a followup.
/// Can be used in [`SpriteImageMode::Scale`]. | ||
#[derive(Debug, Clone, Copy, PartialEq, Default, Reflect)] | ||
#[reflect(Debug)] | ||
pub enum ScalingMode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe this type might be better split up into different two enums, one Fill/Fit
and the other Start/Center/End
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe this type might be better split up into different two enums, one
Fill/Fit
and the otherStart/Center/End
.
I don't think that will be convenient for the end user. There are only six variants and no more are planned to add I guess. That led to the single field in Sprite that corresponds to scaling, adding one more enum adds more complexity.
Then SpriteImageMode::Scale
needs to be a struct enum variant with two fields, one for Fill/Fit
and the second one for direction Start/Center/End
. In my humble opinion, it will not bring convenience for the current API
.
I've been considering how to effectively apply scaling to a specific rectangle within a texture. My approach involves summing the scaling factors and then appropriately adjusting the offsets to accommodate the various scaling options. This way, I can ensure that the scaling is applied uniformly and maintains the correct alignment within the texture. Please take a look. @alice-i-cecile @ickshonpe @BenjaminBrienen Screen.Recording.2025-01-20.at.21.54.54.mov |
@ickshonpe just a gentle ping to review the latest changes. |
Objective
Bevy sprite image mode lacks proportional scaling for the underlying texture. In many cases, it's required. For example, if it is desired to support a wide variety of screens with a single texture, it's okay to cut off some portion of the original texture.
Solution
I added scaling of the texture during the preparation step. To fill the sprite with the original texture, I scaled UV coordinates accordingly to the sprite size aspect ratio and texture size aspect ratio. To fit texture in a sprite the original
quad
is scaled and then the additional translation is applied to place the scaled quad properly.Testing
For testing purposes could be used
2d/sprite_scale.rs
. Also, I am thinking that it would be nice to have some tests for acrates/bevy_sprite/src/render/mod.rs:sprite_scale
.Showcase