4

As answered in this thread, ImageViews can be clipped to achieve corner rounding (Corner rounding of imageViews cannot be achieved with CSS). However, I am trying to have different corner radii, very much like described by this bit of CSS:

.background {
    -fx-background-radius: 64 0 16 0;
}

What i want the image to look like (Image = black area) is pretty much: enter image description here

The problem I am currently facing is that it is not possible to clip imageViews with a VBox, for instance. I could clip, however, the imageView with a Rectangle, but this again, does not give me the possibility to have different corner radii.

How could I achieve the same effect as the above CSS code (which of course does not work with an imageView) and have an image have different corners?

Trizion
  • 262
  • 1
  • 12
  • 1
    You can probably achieve the effect you want via CSS using a [`Region` with a background image](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#region) plus -fx-shape or -fx-background-radius, rather than an ImageView. – jewelsea Jul 12 '22 at 10:15
  • I don't quite understand why the linked question doesn't answer your question. Also even trying to do this "clip imageViews with a VBox" doesn't make sense, VBox is a layout plane. And I don't undertand why clipping with a rectangle would not allow different corner radii, just adjust the arcWidth and arcHeight or the rectangle for different radii. Perhaps I am misundertanding though and you want to have different values for the radius in the corners simultaneously. Adding an image of what you are trying to achieve may help. – jewelsea Jul 12 '22 at 10:18
  • Note that a node-based clip can take any arbitrary shape, and you can use shape addition and subtraction to modify it, as well as using SVGPaths and graded opacity, so it is pretty flexible. – jewelsea Jul 12 '22 at 10:24
  • 1
    Yes, I do want different corner radii simultaneously. Im aware that clipping with a VBox does not make sense, what I was trying to bring across was that a VBox can have different corner radii simultaneously, and I want to achieve a clipping as if such a VBox was used. – Trizion Jul 12 '22 at 10:31
  • "VBox can have different corner radii simultaneously" -> how do you do that? VBox is just a node, so if you can modify the corner radii, you can do that with any node. Do you set the [shape](https://openjfx.io/javadoc/17/javafx.graphics/javafx/scene/layout/Region.html#shapeProperty)? That is possible but might be a little trickier than using CSS settings for backgrounds and background images. – jewelsea Jul 12 '22 at 10:35
  • "how do you do that?" For instance, with CSS, such as that in the question above. Now, using that CSS on an imageView does not have an effect, which is pretty much the problem I am facing. Also, i cannot put the imageView into a VBox with such CSS, the imageView still does not have the cornerRadii of the VBox. – Trizion Jul 12 '22 at 10:41
  • Yes, I understand now. Your issue is that ImageView is not a Region, so none of the Region CSS will apply to it. Also, when placed in a Region, it is not a background, so none of the background CSS applied to the Region will apply to the ImageView. There are a few different ways to solve this, one being the method in the first comment, another being setting a shape on the region and a third using a shaped clip. I can't write up an answer now, but will write one up later if there is not already one. – jewelsea Jul 12 '22 at 10:46
  • 1
    Okay, I will try the "shape-addition-clip" - way, if it works, I will post it as an answer if there is none by then. Also, thanks for your efforts and your suggestion to add an example graphic, i will do that now. – Trizion Jul 12 '22 at 10:51
  • See also [_JavaFX css border-radius issue_](https://stackoverflow.com/q/38004410/230513). – trashgod Jul 12 '22 at 11:38
  • 1
    If you're good at SVG paths, then you could probably do this via CSS and `-fx-shape`. – Slaw Jul 12 '22 at 17:47
  • 2
    Yeah, I'm not... :( Also, I wanted a flexible solution thats reusable for several images with different radii each. And the Util class I wrote does that just fine. – Trizion Jul 12 '22 at 17:49

2 Answers2

5

Clip ImageView with Path

clip imageview javafx

In this approach an ImageView is clipped with a Path . That path will adapt at FitWidth and FitHeight values of each imageView passed to getClip method . The second and third arguments passed are values to calculate radius of top and bottom round corner based on Fitheight .

getClip method will draw a path like so :

path

This is a single class javafx app you can try

App.java

public class App extends Application {

    @Override
    public void start(Stage stage) {

        ImageView imageView = new ImageView("https://ioppublishing.org/wp-content/uploads/2017/03/cat-web-cc0.jpg");
        imageView.setFitHeight(300);
        imageView.setFitWidth(300);
        imageView.setClip(getClip(imageView, 0.1,0.3));

        ImageView imageView1 = new ImageView("https://ioppublishing.org/wp-content/uploads/2017/03/cat-web-cc0.jpg");
        imageView1.setFitHeight(400);
        imageView1.setFitWidth(400);
        imageView1.setClip(getClip(imageView1, 0.2,0.3));

        VBox vBox = new VBox(imageView, imageView1);
        vBox.setSpacing(20);
        vBox.setPadding(new Insets(20));
        vBox.setAlignment(Pos.CENTER);
        vBox.setFillWidth(true);
        var scene = new Scene(new StackPane(vBox), 1024, 800);

        stage.setScene(scene);
        stage.setTitle("clipping with path");
        stage.show();

    }

    public static void main(String[] args) {
        launch();
    }

    private Node getClip(ImageView imageView, double radiusTop,double radiusBot) {
        Path clip;

        double height = imageView.getFitHeight();
        double width = imageView.getFitWidth();
        double radius1 = height * radiusTop;
        double radius2 = height * radiusBot;
        clip = new Path(new MoveTo(0, radius1), new ArcTo(radius1, radius1, 0, radius1, 0, false, true),
                new HLineTo(width),
                new VLineTo(height - radius2),
                new ArcTo(radius2, radius2, 0, width - radius2, height, false, true),
                new HLineTo(0));

        clip.setFill(Color.ALICEBLUE);

        return clip;

    }

}

Update for all corners clip all corners javafx

App.java

public class App extends Application {

    @Override
    public void start(Stage stage) {

        ImageView imageView = new ImageView("https://ioppublishing.org/wp-content/uploads/2017/03/cat-web-cc0.jpg");
        imageView.setFitHeight(300);
        imageView.setFitWidth(300);
        imageView.setClip(getClip(imageView, 0.3, 0.1, 0.2, 0.1));

        ImageView imageView1 = new ImageView("https://ioppublishing.org/wp-content/uploads/2017/03/cat-web-cc0.jpg");
        imageView1.setFitHeight(400);
        imageView1.setFitWidth(400);
        imageView1.setClip(getClip(imageView1, 0.3, 0.2, 0.1, 0.2));

        VBox vBox = new VBox(imageView, imageView1);
        vBox.setSpacing(20);
        vBox.setPadding(new Insets(20));
        vBox.setAlignment(Pos.CENTER);
        vBox.setFillWidth(true);
        var scene = new Scene(new StackPane(vBox), 1024, 800);

        stage.setScene(scene);
        stage.setTitle("clipping with path");
        stage.show();

    }

    public static void main(String[] args) {
        launch();
    }

    private Node getClip(ImageView imageView, double topLeft, double topRight, double bottomLeft, double bottomRight) {
        Path clip;

        double height = imageView.getFitHeight();
        double width = imageView.getFitWidth();
        double radius1 = height * topLeft;
        double radius2 = height * topRight;
        double radius3 = height * bottomLeft;
        double radius4 = height * bottomRight;

        clip = new Path(new MoveTo(0, radius1),
                new ArcTo(radius1, radius1, 0, radius1, 0, false, true),
                new HLineTo(width - radius2),
                new ArcTo(radius2, radius2, 0, width, radius2, false, true),
                new VLineTo(height - radius4),
                new ArcTo(radius4, radius4, 0, width - radius4, height, false, true),
                new HLineTo(radius3),
                new ArcTo(radius3, radius3, 0, 0, height - radius3, false, true));

        clip.setFill(Color.ALICEBLUE);

        return clip;

    }

}
Giovanni Contreras
  • 2,345
  • 1
  • 13
  • 22
  • I you could add the remaining two corners to the getClip method i will happily accept your answer :) – Trizion Jul 13 '22 at 05:12
  • That was not in your question – Giovanni Contreras Jul 13 '22 at 12:27
  • Sure, but it was in the last comment I left under my question. Also, others might have the same problem, but with other corners. See https://stackoverflow.com/questions/30728513/in-javafx-is-it-possible-to-make-rounded-corner-of-a-rectangle-except-right-bott - I think your solution would be great if it worked for all these cases! – Trizion Jul 13 '22 at 13:22
  • 2
    I'll add another version for all corners – Giovanni Contreras Jul 13 '22 at 16:14
4

One possible solution is to add several shapes and clip the imageView with these added shapes. The below Util class does exactly that. To achieve the effect the CSS code -fx-background-radius: 64 0 16 0 would have (if it worked for imageViews), you would call:

ImageView rounded = ImageUtils.round(image, 64, 0, 16, 0);

The way the Util class works is that it generates four different rectangles for each corner with the corresponding radius, and makes these four rectangles overlap in the center like this:

enter image description here

It then puts these in a group and clips an ImageView with that group:

public class ImageUtils {

    public static ImageView round(Image image,
                              double topLeft,
                              double topRight,
                              double bottomRight,
                              double bottomLeft) {
        topLeft *= 2;
        topRight *= 2;
        bottomRight *= 2;
        bottomLeft *= 2;

        double width = image.getWidth();
        double height = image.getHeight();

        double topEdgeHalf = (width - topLeft - topRight) / 2;
        double rightEdgeHalf = (height - topRight - bottomRight) / 2;
        double bottomEdgeHalf = (width - bottomLeft - bottomRight) / 2;
        double leftEdgeHalf = (height - topLeft - bottomLeft) / 2;

        double topLeftWidth = topEdgeHalf + 2 * topLeft;
        double topLeftHeight = leftEdgeHalf + 2 * topLeft;
        double topLeftX = 0;
        double topLeftY = 0;

        double topRightWidth = topEdgeHalf + 2 * topRight;
        double topRightHeight = rightEdgeHalf + 2 * topRight;
        double topRightX = width - topRightWidth;
        double topRightY = 0;

        double bottomRightWidth = bottomEdgeHalf + 2 * bottomRight;
        double bottomRightHeight = rightEdgeHalf + 2 * bottomRight;
        double bottomRightX = width - bottomRightWidth;
        double bottomRightY = height - bottomRightHeight;

        double bottomLeftWidth = bottomEdgeHalf + 2 * bottomLeft;
        double bottomLeftHeight = leftEdgeHalf + 2 * bottomLeft;
        double bottomLeftX = 0;
        double bottomLeftY = height - bottomLeftHeight;

        Rectangle topLeftRect = new Rectangle(topLeftX, topLeftY, topLeftWidth, topLeftHeight);
        Rectangle topRightRect = new Rectangle(topRightX, topRightY, topRightWidth, topRightHeight);
        Rectangle bottomRightRect = new Rectangle(bottomRightX, bottomRightY, bottomRightWidth, bottomRightHeight);
        Rectangle bottomLeftRect = new Rectangle(bottomLeftX, bottomLeftY, bottomLeftWidth, bottomLeftHeight);

        topLeftRect.setArcWidth(topLeft);
        topLeftRect.setArcHeight(topLeft);
        topRightRect.setArcWidth(topRight);
        topRightRect.setArcHeight(topRight);
        bottomRightRect.setArcWidth(bottomRight);
        bottomRightRect.setArcHeight(bottomRight);
        bottomLeftRect.setArcWidth(bottomLeft);
        bottomLeftRect.setArcHeight(bottomLeft);

        Group clipGroup = new Group(
                topLeftRect,
                topRightRect,
                bottomRightRect,
                bottomLeftRect
        );

        ImageView clipped = new ImageView(image);
        clipped.setClip(clipGroup);
        return clipped;
    }
}
Trizion
  • 262
  • 1
  • 12