Skip to content

Android: honour per-corner border radii on images - #55

Open
gwleuverink wants to merge 2 commits into
NativePHP:mainfrom
trailhead-labs:fix/355-image-per-corner-radii
Open

Android: honour per-corner border radii on images#55
gwleuverink wants to merge 2 commits into
NativePHP:mainfrom
trailhead-labs:fix/355-image-per-corner-radii

Conversation

@gwleuverink

@gwleuverink gwleuverink commented Aug 20, 2026

Copy link
Copy Markdown

Fixes NativePHP/mobile-air#355.

<native:image> ignored per-corner radii on Android. rounded-3xl rounded-br-none gave a fully rounded image next to a column with the sharp corner, and rounded-tl-3xl rounded-tr-3xl rendered the image completely square. iOS was already correct, because it clips in the shared NodeStyleModifier rather than in the renderer.

The renderer only ever saw the uniform radius:

val imgModifier = if (radius > 0f) modifier.clip(RoundedCornerShape(radius.dp)) else modifier

Containers don't do that. They go through nodeShape(radius, props), which reads the radius_tl / tr / br / bl props PHP emits whenever any corner is authored. So the image now calls the same helper:

val hasCorners = p.has("radius_tl") || radius > 0f
val imgModifier = if (hasCorners) modifier.clip(nodeShape(radius, p)) else modifier

The guard matters as much as the shape. A node with only per-corner radii carries no uniform radius, so the old radius > 0f check skipped clipping altogether, which is why the top-corners-only case came out square instead of half right.

The version bump

nodeShape shipped in mobile 4.2.0 while this package requires ^4.0, so I bumped it to ^4.2.

That constraint is already wrong on main, though: NativeUIBackgroundLayerHost.kt imports LocalBackgroundLayerPresent, which first shipped in 4.1.0. Install this plugin against mobile 4.0.x today and its Kotlin fails to compile in the app build. CI won't tell you either, the workflow parses Swift only and leaves Android syntax to the consuming app build. So the bump also closes a break that predates this PR.

Verification

A column and an image side by side on identical classes, five cases, on an API 36 emulator:

@php
    $cases = [
        'A rounded-3xl rounded-br-none' => 'rounded-3xl rounded-br-none',
        'B rounded-tl-3xl rounded-tr-3xl' => 'rounded-tl-3xl rounded-tr-3xl',
        'C rounded-3xl (control)' => 'rounded-3xl',
        'D rounded-full (oversize)' => 'rounded-full',
        'E rounded-3xl rounded-br-full' => 'rounded-3xl rounded-br-full',
    ];
@endphp

@foreach ($cases as $label => $classes)
    <native:text class="text-sm font-extrabold">{{ $label }}</native:text>

    <native:row class="w-full gap-4">
        <native:column class="w-[150] h-[90] bg-red-400 {{ $classes }}" />
        <native:image src="{{ $src }}" :fit="2" class="w-[150] h-[90] {{ $classes }}" />
    </native:row>
@endforeach

D and E are there for the clamp: a radius larger than the element. The image clamps the same way the column does, pill for pill and swoop for swoop.

image

iOS rebuilt and compared pixel for pixel against the pre-fix screenshot over the area holding the rows, no difference. Nothing in this diff touches an iOS path.

No test comes with this. The .kt files here are loose sources compiled inside the consuming app, so there's no harness that could cover a renderer, and the wire contract the guard depends on is already pinned in mobile-air's TailwindParserTest (all four corners emitted whenever any is authored, each resolved against the uniform radius).

@gwleuverink
gwleuverink marked this pull request as ready for review August 20, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Masterclass] Android: <native:image> ignores per-corner border radii (containers honour them)

1 participant