JafaFX radial gradient - css

I would like to reproduce the following screen with JavaFX and tried this (stripped) code:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hello World");
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitHint("");
AnchorPane root = new AnchorPane();
root.setStyle("-fx-background-color: " +
"rgb(15,37,74), " +
"radial-gradient(center 50% 50%, radius 65%, rgb(97,156,188) 10%, rgb(22,51,93));");
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
However I have the following problems to reproduce the original picture:
I am not able to generate a circular gradient, since the AnchorPane is a rectangle, the gradient is elliptic. As far I understand the documentation a circular gradient is not possible?
The gradient of the original picture is not a linear but has some circles (a bright circle in the middle and a medium circle outside). I tried to play around and was adding percentage to the stop colors, but when I do (eg. rgb(97,156,188) 30%, rgb(22,51,93)) I get a much brighter visualization of the ellipse which I don't understand. The start of the gradient is always brighter as it should be and I don't know why:
Can anyone explain me whats going on here?

Downloading the (800x600 pixel) image from the screen shot you are looking to duplicate, and using a color picker, I get the following (decimal r,g,b) values, traversing from the center (400, 300) to the left edge (0, 300):
(400, 300): (104, 163, 193)
(300, 300): (87, 145, 182)
(200, 300): (60, 113, 163)
(100, 300): (38, 85, 137)
(0, 300): (23, 56, 99)
That looks close enough to linear to me (there maybe some general fuzziness and jpeg compression will change the values a little).
So this seems a good approximation:
root.setStyle("-fx-background-color: radial-gradient(center 50% 50%, radius 50%, rgb(104, 163, 193) 0%, rgb(23, 56, 99) 100%);");
I don't see a way to make it circular using percentages: you would have to use actual lengths. If the size may change, then of course you need to respond to that; you can do this with a binding as follows:
root.styleProperty().bind(Bindings.createStringBinding(() ->
String.format("-fx-background-color: radial-gradient(center %dpx %dpx, radius %dpx, rgb(104, 163, 193) 0%%, rgb(23, 56, 99) 100%%);",
(int) (root.getWidth()/2), (int) (root.getHeight()/2), (int)(Math.max(root.getWidth(), root.getHeight())/2)),
root.widthProperty(), root.heightProperty()));
Be aware that this is not a high-performing way of doing this; inline styles are generally the slowest way to apply CSS to a node, and here you are changing the inline style on every small change in dimension of the node. However, it appears to work just fine on my system; just be aware it might not be a good way to apply a gradient to many nodes in the same view.

Related

How do you bump-map large areas in a JavaFX Canvas

I want to apply some lighting effects using JavaFX onto a canvas GraphicsContext. At first I used the Lighting.bumpInput to pass a static bump-map for lighting. However this only lights a certain area of my canvas correct.
So now I do the lighting manually by first drawing all bump-maps onto the canvas then applying my lighting effect and finally blending all the original images with BlendMode.MULTIPLY together with the lit bump-maps.
This does not look as nice as the first approach but it does its job.
Now there is still the problem that the lighting effect (Pointlight) is applied across the whole canvas no matter how big the area is.
public void start(Stage primaryStage) {
Image texture = new Image("...");
Image bumpMap = new Image("...");
Light.Point pointLight = new Light.Point(256, 256, 25, Color.WHITE);
Lighting lighting = new Lighting(pointLight);
Canvas canvas = new Canvas(1280, 800);
canvas.getGraphicsContext().setGlobalBlendMode(BlendMode.MULTIPLY);
canvas.getGraphicsContext().drawImage(bumpMap, 0, 0);
canvas.getGraphicsContext().applyEffect(lighting);
canvas.getGraphicsContext().drawImage(texture, 0, 0);
Scene scene = new Scene(new AnchorPane(canvas), 1280, 800);
primaryStage.setScene(scene);
primaryStage.show();
}
Now my question is, what is the proper way in JavaFX to apply the lighting only to a specific area on my canvas? Like to define the radius of my point light?
Well, the intensity of the light is proportional to 1/r³, wheras r is the distance (radius) vom the light source to the object. Hence you can affect the lit area by varying the radius, in your case the z-coordinate (=25) of your light source :
Light.Point pointLight = new Light.Point(256, 256, 25, Color.WHITE);
However, by doing this you will actually still have a steady transition of illumination on your canvas, there is no sharp border of illuminated and non-illuminated areas. No matter, how big you choose the value of r, the intensity will never be exactly zero.
If u want to have illumination only applied to a certain area of your canvas, I would recommend some sort of masking-workaround, i.e. duplicate your image and make for example a hole with Radius = r on it without applying lighting, then place it as overlay-mask onto your original image with all your lighting filters.

JavaFX - What is the correct way to give a Shape subclass object like Rectangle no Color

I found out that a way to do this is to simply just pass null to setFill(Color color) but that seems like a hacker solution to me. I am wondering if there is a better/proper way to do this?
CubicCurve cubicCurve = new CubicCurve(
50,
75,
80,
-25,
110,
175,
140,
75);
cubicCurve.setStrokeType(StrokeType.CENTERED);
cubicCurve.setStroke(Color.BLACK);
cubicCurve.setStrokeWidth(3);
cubicCurve.setFill(null); //this is in my opinion a hacker solution
That also brings me to my second question, why is the default fill color white for any shape? Is there an efficiency reason for this? What is the point?
A more elegant way to write it would be to assign the fill as TRANSPARENT for a similar effect.
cubicCurve.setFill(Color.TRANSPARENT);
As far as your other questions are concerned, the default fill is Black and not White. I have no idea about the reason behind it. This is how the JavaFX developers decided it to be.
In the Shape.java :
public final Paint getFill() {
return fill == null ? Color.BLACK : fill.get();
}

How do you create a rounded border with a semi transparent background for a button in JavaFX?

I want to create buttons like this in JavaFX (not html).
http://jsfiddle.net/x7dRU/3/
(hover on them to see the effect)
[Stupid Stackoverflow insists on me posting jsfiddle code here which isn't relevant]
<li>Button 1</li>
So with a rounded border and a transparent background. Unfortunately the background/insets technique seems to overwrite the content from outside to in. So if you draw a bright border, then you can't undo the brightness to create a dark&transparent background without hardcoding the colour. I.e. it's not write once, run everywhere, on different coloured panels.
-fx-border-color doesn't seem to support rounding or at least isn't recommended here Set border size . I imagine the rounding of the border doesn't sync with the rounding of the background.
Seems HTML5 has the edge on this one then. Tell me I'm wrong :-) ...although I suspect my question can't be done without specifying the colour for each and every button context.
Browny points.
Note, I realise I've coloured the white border greenish (context sensitive), I'm happy with a border of semi-transparent white as a solution. First prize would be a burn/dodge/etc(background-colour) function ala photoshop.
Plan B.
It doesn't look so bad without rounded edges, so maybe I should just resort to -fx-border-color
Background info
Have a look at the information in the css documentation on "looked-up colors"(scroll down a little, beyond the named color section).
The way these basically work, is that you can define a "looked up color" (i.e. a color-valued variable) and apply it to a node in the scene graph. The value is inherited by any node descended from that node in the scene graph.
Now have a browse through the default style sheet, modena.css. The way this works is that almost everything is defined in terms of a very short list of looked-up colors. The consequence is that you can readily "theme" your applications just by redefining those colors on the root of the scene. Try adding this stylesheet to your favorite JavaFX application:
.root {
-fx-base: #c7dec7;
-fx-accent: #00c996 ;
-fx-default-button: #abedd8 ;
-fx-focus-color: #03d39e;
-fx-faint-focus-color: #03d39e22;
}
As you've observed, -fx-border is not used at all in the default stylesheet; instead borders are implemented by defining "nested" background colors which are essentially rectangular fills laid on top of each other. This is apparently more efficient (quite considerably so, by my understanding). So you are correct that making the inner of two backgrounds transparent will simply reveal the "outer" border color, not the color of the background.
How to achieve what you're looking for
The background of a pane defaults to the looked-up color -fx-background, which in turn defaults to a lighter version of -fx-base. So if you stick to changing the color of the pane containing the buttons by changing -fx-background or -fx-base, then you can make the button appear transparent by setting its background to
-fx-background-color: (some-border-color), -fx-background ;
The default borders for buttons contain three values; -fx-shadow-highlight, -fx-outer-border, and -fx-inner-border. You could override the values for these individually, or just redefine the background color as you need.
An approximation to what you want is in this example: you can mess with the exact values for the thickness of the border (from the second -fx-background-insets value) and the radius of the corners to get it as you need. If you want to get fancy with it, play with combinations of ladders and gradients.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StyledButtonExample extends Application {
private int currentColorIndex = 0 ;
private final String[] baseColors = new String[] {"#8ec919", "#bfe7ff", "#e6e6fa",
"#ffcfaf", "#fff7f7", "#3f474f"};
private StackPane root ;
#Override
public void start(Stage primaryStage) {
root = new StackPane();
Button button = new Button("Change colors");
button.setOnAction(event -> changeColors());
root.getChildren().add(button);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("transparent-button.css").toExternalForm());
root.setStyle("-fx-base: "+baseColors[0]+";");
primaryStage.setScene(scene);
primaryStage.show();
}
private void changeColors() {
currentColorIndex = (currentColorIndex + 1) % baseColors.length ;
root.setStyle("-fx-base: "+baseColors[currentColorIndex]+";");
}
public static void main(String[] args) {
launch(args);
}
}
transparent-button.css:
.button {
-fx-background-color: derive(-fx-base, 60%), -fx-background ;
-fx-background-insets: 0, 1px ;
-fx-background-radius: 4px, 0 ;
}
.button:hover {
-fx-background-color: #fff, derive(-fx-background, -5%) ;
}

QLinearGradient doesn't work properly with QBrush

I want to draw 1 digit on the screen by the graphic framework classes. I want the fill approach of '1' to be something like
(source: qt-project.org)
but the brush of my drawn '1' is just like a yellow SolidBrush by the below code (an ugly bold yellow '1'). Can you help me what's wrong with it?
QGraphicsSimpleTextItem digit_1 = new QGraphicsSimpleTextItem;
digit_1->setText(QString::number(1));
digit_1->setPen(QPen(QColor("black")));
QLinearGradient gradient(digit_1->boundingRect().topLeft(),
digit_1->boundingRect().bottomRight());
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::yellow); // yellow is for example
QBrush brush(gradient);
brush.setStyle(Qt::BrushStyle::LinearGradientPattern);
digit_1->setBrush(brush);
digit_1->setFont(QFont("courier", 35, QFont::Black));
Thanks in advanced.
Your issue most likely comes from the fact that you're basing your gradient's "area" on the bounding rect of your item before you set the font size to something much larger than the default.
The bounding rect you're getting is thus much smaller than your actual bounding rect. Since the default spread method is padding, you're seeing most likely just one color (or not enough of the gradient for it to be actually visible).
So move your setFont call to the top, before you create the gradient. You can drop the setStyle on your brush, that's determined automatically from the gradient. (In fact, you can drop that brush entirely and use the gradient in setBrush.)
With the way you set up the gradient, you'll get a "diagonal" gradient. If you want it from top to bottom, use the top left and bottom left points instead.
Demo
#include <QtGui>
class W: public QGraphicsView
{
Q_OBJECT
public:
W(QWidget *parent = 0)
: QGraphicsView(parent)
{
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem;
item->setText("Stack Overflow");
item->setPen(QPen(Qt::red));
item->setFont(QFont("courier", 60, QFont::Bold));
QLinearGradient lgrad(item->boundingRect().topLeft(),
item->boundingRect().bottomLeft());
lgrad.setColorAt(0.0, Qt::red);
lgrad.setColorAt(1.0, Qt::yellow);
item->setBrush(lgrad);
QGraphicsScene *scene = new QGraphicsScene;
scene->setBackgroundBrush(QBrush(Qt::black));
scene->addItem(item);
setScene(scene);
}
};

Should i re-draw SurfaceLayer on every frame?

I've create simple example: background surface layer and 10 small "dots" on it (10 surface layers 10x10 px each filled with color via fillRect()). Paint method simply moves the dots around periodically:
private SurfaceLayer background;
private List<Layer> dots = new ArrayList<Layer>();
#Override
public void init()
{
background = graphics().createSurfaceLayer(graphics().width(), graphics().height());
background.surface().setFillColor(Color.rgb(100, 100, 100));
background.surface().fillRect(0, 0, graphics().width(), graphics().height());
graphics().rootLayer().add(background);
for (int i = 0; i < 10; i++)
{
SurfaceLayer dot = graphics().createSurfaceLayer(10, 10);
dot.surface().clear();
dot.surface().setFillColor(Color.rgb(250, 250, 250));
dot.surface().fillRect(0, 0, 10, 10);
dot.setDepth(1);
dot.setTranslation(random()*graphics().width(), random()*graphics().height());
dots.add(dot);
graphics().rootLayer().add(dot);
}
}
#Override
public void paint(float alpha)
{
for (Layer dot : dots)
{
if (random() > 0.999)
{
dot.setTranslation(random()*graphics().width(), random()*graphics().height());
}
}
}
Somehow java version draws all dots while html and android version draw only 1.
Manual doesn't clearly say if i should re-draw all these dots in every paint() call. And as far as i understood SurfaceLayer is meant for cases when you do not modify layer on every frame (so same buffer can be reused?), but this doesn't work.
So can you guys help me with correct SurfaceLayer usage? If i just filled a rectangular on SurfaceLayer - would it ramin on this layer forever or should i fill it in each paint call? If yes - is this different from ImmeadiateLayer?
You don't need to redraw a surface layer on every call to paint. As you have shown, you draw it only when preparing it, and the texture into which you've draw will be rendered every frame without further action on your part.
If the Android and HTML backend are not drawing all of your surface layers, there must be a bug. I'll try to reproduce your test and see if it works for me.
One note: creating a giant surface the size of the screen and drawing a solid color into it is a huge waste of texture memory. Just create an ImmediateLayer that calls fillRect() on every frame, which is far more efficient than creating a massive screen-covering texture.

Resources