Improve nine-slice atlas sampling
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Added TexturePacker frame, manual source-region, and nine-slice image rendering fields for image-capable nodes.
|
- Added TexturePacker frame, manual source-region, and nine-slice image rendering fields for image-capable nodes.
|
||||||
- Fixed nine-slice image seams by overlapping destination slices during runtime rendering.
|
- Fixed nine-slice image seams by overlapping destination slices and using inset source sampling during runtime rendering.
|
||||||
- Fixed Runtime alpha inheritance so parent fade commands apply to the full child subtree.
|
- Fixed Runtime alpha inheritance so parent fade commands apply to the full child subtree.
|
||||||
- Added Runtime text shadow fields for text-capable nodes.
|
- Added Runtime text shadow fields for text-capable nodes.
|
||||||
- Fixed Runtime node color alpha composition so `#AARRGGBB` alpha now multiplies with node/runtime alpha instead of being overwritten.
|
- Fixed Runtime node color alpha composition so `#AARRGGBB` alpha now multiplies with node/runtime alpha instead of being overwritten.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ List<({Rect source, Rect destination})> runtimeNineSliceRects({
|
|||||||
double sliceRight = 0,
|
double sliceRight = 0,
|
||||||
double sliceBottom = 0,
|
double sliceBottom = 0,
|
||||||
double destinationOverlap = 0,
|
double destinationOverlap = 0,
|
||||||
|
double sourceInset = 0,
|
||||||
}) {
|
}) {
|
||||||
if (source.width <= 0 ||
|
if (source.width <= 0 ||
|
||||||
source.height <= 0 ||
|
source.height <= 0 ||
|
||||||
@@ -88,7 +89,7 @@ List<({Rect source, Rect destination})> runtimeNineSliceRects({
|
|||||||
final parts = <({Rect source, Rect destination})>[];
|
final parts = <({Rect source, Rect destination})>[];
|
||||||
for (var y = 0; y < 3; y++) {
|
for (var y = 0; y < 3; y++) {
|
||||||
for (var x = 0; x < 3; x++) {
|
for (var x = 0; x < 3; x++) {
|
||||||
final sourcePart = Rect.fromLTRB(
|
final rawSourcePart = Rect.fromLTRB(
|
||||||
sourceXs[x],
|
sourceXs[x],
|
||||||
sourceYs[y],
|
sourceYs[y],
|
||||||
sourceXs[x + 1],
|
sourceXs[x + 1],
|
||||||
@@ -100,12 +101,17 @@ List<({Rect source, Rect destination})> runtimeNineSliceRects({
|
|||||||
destXs[x + 1],
|
destXs[x + 1],
|
||||||
destYs[y + 1],
|
destYs[y + 1],
|
||||||
);
|
);
|
||||||
if (sourcePart.width <= 0 ||
|
if (rawSourcePart.width <= 0 ||
|
||||||
sourcePart.height <= 0 ||
|
rawSourcePart.height <= 0 ||
|
||||||
rawDestPart.width <= 0 ||
|
rawDestPart.width <= 0 ||
|
||||||
rawDestPart.height <= 0) {
|
rawDestPart.height <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
final sourcePart = _insetNineSliceSourceRect(
|
||||||
|
rawSourcePart,
|
||||||
|
bounds: source,
|
||||||
|
inset: sourceInset,
|
||||||
|
);
|
||||||
final destPart = _overlapNineSliceDestinationRect(
|
final destPart = _overlapNineSliceDestinationRect(
|
||||||
rawDestPart,
|
rawDestPart,
|
||||||
x: x,
|
x: x,
|
||||||
@@ -119,6 +125,22 @@ List<({Rect source, Rect destination})> runtimeNineSliceRects({
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rect _insetNineSliceSourceRect(
|
||||||
|
Rect rect, {
|
||||||
|
required Rect bounds,
|
||||||
|
required double inset,
|
||||||
|
}) {
|
||||||
|
if (inset <= 0 || rect.width <= inset * 2 || rect.height <= inset * 2) {
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
return Rect.fromLTRB(
|
||||||
|
math.min(rect.right, math.max(bounds.left, rect.left + inset)),
|
||||||
|
math.min(rect.bottom, math.max(bounds.top, rect.top + inset)),
|
||||||
|
math.max(rect.left, math.min(bounds.right, rect.right - inset)),
|
||||||
|
math.max(rect.top, math.min(bounds.bottom, rect.bottom - inset)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Rect _overlapNineSliceDestinationRect(
|
Rect _overlapNineSliceDestinationRect(
|
||||||
Rect rect, {
|
Rect rect, {
|
||||||
required int x,
|
required int x,
|
||||||
@@ -575,7 +597,13 @@ class RuntimeComponent extends PositionComponent
|
|||||||
..color = composeRuntimeColorAlpha(Colors.white, renderAlpha);
|
..color = composeRuntimeColorAlpha(Colors.white, renderAlpha);
|
||||||
final source = _imageSourceRect(image, _currentImageFrame(_node));
|
final source = _imageSourceRect(image, _currentImageFrame(_node));
|
||||||
if (_usesNineSlice(source, rect)) {
|
if (_usesNineSlice(source, rect)) {
|
||||||
_drawNineSliceImage(canvas, image, source, rect, imagePaint);
|
_drawNineSliceImage(
|
||||||
|
canvas,
|
||||||
|
image,
|
||||||
|
source,
|
||||||
|
rect,
|
||||||
|
imagePaint..filterQuality = FilterQuality.none,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
canvas.drawImageRect(image, source, rect, imagePaint);
|
canvas.drawImageRect(image, source, rect, imagePaint);
|
||||||
}
|
}
|
||||||
@@ -630,7 +658,8 @@ class RuntimeComponent extends PositionComponent
|
|||||||
final parts = runtimeNineSliceRects(
|
final parts = runtimeNineSliceRects(
|
||||||
source: source,
|
source: source,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
destinationOverlap: 0.5,
|
destinationOverlap: 1,
|
||||||
|
sourceInset: 0.5,
|
||||||
sliceLeft: _node.sliceLeft ?? 0,
|
sliceLeft: _node.sliceLeft ?? 0,
|
||||||
sliceTop: _node.sliceTop ?? 0,
|
sliceTop: _node.sliceTop ?? 0,
|
||||||
sliceRight: _node.sliceRight ?? 0,
|
sliceRight: _node.sliceRight ?? 0,
|
||||||
|
|||||||
@@ -284,6 +284,36 @@ void main() {
|
|||||||
expect(parts[4].source, const Rect.fromLTRB(10, 10, 20, 20));
|
expect(parts[4].source, const Rect.fromLTRB(10, 10, 20, 20));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('insets nine-slice source rects to avoid atlas edge sampling', () {
|
||||||
|
final parts = runtimeNineSliceRects(
|
||||||
|
source: const Rect.fromLTWH(10, 20, 30, 30),
|
||||||
|
destination: const Rect.fromLTWH(0, 0, 90, 90),
|
||||||
|
sliceLeft: 10,
|
||||||
|
sliceTop: 10,
|
||||||
|
sliceRight: 10,
|
||||||
|
sliceBottom: 10,
|
||||||
|
sourceInset: 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parts.first.source, const Rect.fromLTRB(10.5, 20.5, 19.5, 29.5));
|
||||||
|
expect(parts[4].source, const Rect.fromLTRB(20.5, 30.5, 29.5, 39.5));
|
||||||
|
expect(parts.last.source, const Rect.fromLTRB(30.5, 40.5, 39.5, 49.5));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps tiny nine-slice source rects when inset would collapse them', () {
|
||||||
|
final parts = runtimeNineSliceRects(
|
||||||
|
source: const Rect.fromLTWH(0, 0, 3, 3),
|
||||||
|
destination: const Rect.fromLTWH(0, 0, 30, 30),
|
||||||
|
sliceLeft: 1,
|
||||||
|
sliceTop: 1,
|
||||||
|
sliceRight: 1,
|
||||||
|
sliceBottom: 1,
|
||||||
|
sourceInset: 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(parts[4].source, const Rect.fromLTRB(1, 1, 2, 2));
|
||||||
|
});
|
||||||
|
|
||||||
test('updates text alpha style without rebuilding text component', () {
|
test('updates text alpha style without rebuilding text component', () {
|
||||||
final component = RuntimeComponent(
|
final component = RuntimeComponent(
|
||||||
node: const RuntimeNode(
|
node: const RuntimeNode(
|
||||||
|
|||||||
Reference in New Issue
Block a user