javafx how fill grid with random colors - java

I have a grid.
Now the background of the grid is lightGray.
But how can I get the background of the grid with random colors? So not one color.
How can I make this?
Can someone help me?
thanks in advance.
the code is:
public class Grid2 extends Application {
double gridSize = 20;
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Group(), 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
scene.setFill(createGridPattern());
}
public ImagePattern createGridPattern() {
double w = gridSize;
double h = gridSize;
Canvas canvas = new Canvas(w, h);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.BLACK);
gc.setFill(Color.LIGHTGRAY.deriveColor(1, 1, 1, 0.2));
gc.fillRect(0, 0, w, h);
gc.strokeRect(0, 0, w, h);
Image image = canvas.snapshot(new SnapshotParameters(), null);
ImagePattern pattern = new ImagePattern(image, 0, 0, w, h, false);
return pattern;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

You can probably add a new Jpanel() object of each cell of your grid to get a different color. By making different component of group of some specific cells or just even one cell, you can have different background color for the same.

Related

Rectangle with Gradient Color in javafx

I am using anchorpane in my javafx application. I want to draw a rectangle and fill it with gradient color like this: the left side of the rectangle is blue and the right side is red and I want it to seem that from left to right, the blue color decreases and red color increases.
I know how to put a rectangle ( how to use the Rectangle class in javafx ) but I don't know how to fill it this way. Any ideas?
See docs on linear gradients.
public class Gradient extends Application {
public static final double S = 100;
#Override
public void start(Stage stage) {
Stop[] stops = new Stop[] {
new Stop(0, Color.BLUE),
new Stop(1, Color.RED)
};
LinearGradient gradient = new LinearGradient(
0, 0,
1, 0,
true,
CycleMethod.NO_CYCLE,
stops
);
Rectangle rectangle = new Rectangle(S, S, gradient);
stage.setScene(new Scene(new Group(rectangle)));
stage.show();
}
public static void main(String[] args) {
launch();
}
}

Crop JavaFX Image to square or Circle

I need a function.
import javafx.scene.image.Image;
private Image cropImage(Image image) {
// Cut Image to max Circle or square
return Image;
}
The important thing is that I don't want to change the scale of the image but cut the maximum circle or square in the middle of the image.
I have been trying to do this for two days now.
Can you help me with this?
Thank you very much. Rene
Something like this:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Osias_Beert_the_Elder_-_Dishes_with_Oysters%2C_Fruit%2C_and_Wine_-_Google_Art_Project.jpg/350px-Osias_Beert_the_Elder_-_Dishes_with_Oysters%2C_Fruit%2C_and_Wine_-_Google_Art_Project.jpg");
Image img2 = crop(img, false);
Image img3 = crop(img, true);
HBox box = new HBox(8, new ImageView(img),new ImageView(img2),new ImageView(img3));
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
private Image crop(Image img, boolean toCircle) {
double d = Math.min(img.getWidth(),img.getHeight());
double x = (d-img.getWidth())/2;
double y = (d-img.getHeight())/2;
Canvas canvas = new Canvas(d, d);
GraphicsContext g = canvas.getGraphicsContext2D();
if (toCircle) {
g.fillOval(0, 0, d, d);
g.setGlobalBlendMode(BlendMode.SRC_ATOP);
}
g.drawImage(img, x, y);
return canvas.snapshot(null, null);
}
}
You can do that via the setClip method of the ImageView.

How do I double-buffer in Java Swing on a Retina display without losing the higher resolution?

I'm using double-buffered graphics in my JLayer subclass to implement a simple swipe animation in a Java Swing application. It works fine on the older displays, but when I run it on a Retina display, the screen loses the doubled-resolution when the animation starts, and gets it back when it ends. I'm not sure how to maintain the higher resolution during the animation.
My animate method originally looked like this:
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> { /* omitted for brevity */ };
timer.addActionListener(actionListener);
int imageType = BufferedImage.TYPE_INT_ARGB;
upcomingScreen = new BufferedImage(liveComponent.getWidth(), liveComponent.getHeight(), imageType);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D); // liveComponent is a JComponent
graphics2D.dispose();
timer.start();
}
I tried doubling the image size, but that didn't help.
upcomingScreen = new BufferedImage(liveComponent.getWidth()*2, liveComponent.getHeight()*2, imageType);
To reflect these changes, I changed my drawing code in LayerUI by doubling xLimit, width, height:
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth()*2 * frame) / maxFrames;
int width = c.getWidth()*2;
int height = c.getHeight()*2;
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
super.paint(g, c);
}
}
This doesn't help. It draws the same with or without this last change, which makes no sense.
Here is a class that illustrates the problem:
/**
* <p>Created by IntelliJ IDEA.
* <p>Date: 5/2/20
* <p>Time: 10:25 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings({"HardcodedLineSeparator", "StringConcatenation", "HardCodedStringLiteral", "DuplicatedCode"})
public final class SwipeViewTest extends JPanel {
public static final String text1 = "Demo of Swipe View.\n\nThe swipe button will toggle between two pages of text. It has a built-in " +
"special effect, which is a swipe. When you hit the swipe button, it should flip between two pages of text. This worked fine on " +
"the older displays, but for some reason, on a Retina display, the text briefly switches to low resolution as the swipe proceeds, " +
"then switches back once it has finished. This code is written for retina displays. I don't know if it will work for the older, " +
"low resolution displays.\n\nYou can watch it swipe by hitting the space bar or by clicking the swipe button.";
public static final String text2 = "Demo of Swipe View.\n\nThis is the second page of the swipe-text demo. The change in resolution is " +
"most easily noticed when watching the line at the top, which doesn't change as the swipe is performed.";
private final SwipeView<TestView> swipeView;
private final TestView testView;
public static void main(String[] args) {
JFrame frame = new JFrame("SwipeView demo");
SwipeViewTest comp = new SwipeViewTest();
comp.install();
frame.add(comp);
frame.setLocationByPlatform(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private boolean page1 = true;
private SwipeViewTest() {
super(new BorderLayout());
testView = new TestView();
swipeView = SwipeView.wrap(testView, 1000);
add(BorderLayout.CENTER, swipeView.getLayer());
}
private void install() {
JButton jButton = new JButton("Swipe");
jButton.addActionListener(this::doSwipe);
add(jButton, BorderLayout.PAGE_END);
AncestorListener ancestorListener = new AncestorListener() {
#Override
public void ancestorAdded(final AncestorEvent event) {
JComponent button = event.getComponent();
button.requestFocus();
button.removeAncestorListener(this); // execute only once.
}
#Override public void ancestorRemoved(final AncestorEvent event) { }
#Override public void ancestorMoved(final AncestorEvent event) { }
};
jButton.addAncestorListener(ancestorListener);
}
private void doSwipe(ActionEvent ignored) {
swipeView.swipeLeft(this::flipPage);
}
private void flipPage() {
page1 = !page1;
if (page1) {
testView.setText(text1);
} else {
testView.setText(text2);
}
}
private static class TestView extends JPanel {
private final JTextArea textArea;
TestView() {
super(new BorderLayout());
textArea = new JTextArea(20, 40);
JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setText(text1);
add(scrollPane, BorderLayout.CENTER);
}
private void setText(String text) {
textArea.setText(text);
}
}
/**
* SwipeView adds a swipe special effect to a Component. This draws a swipe-right or swipe-left effect on a chosen
* action. It also optionally supports a repeated action when the mouse is held down.
* <p>
* This class is very specific right now, but I hope to generalize it for other special effects later.
* <p>Created by IntelliJ IDEA.
* <p>Date: 4/4/18
* <p>Time: 12:38 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings("MagicNumber")
public static final class SwipeView<C extends JComponent> extends LayerUI<C> {
public static <J extends JComponent> SwipeView<J> wrap(J view, int durationMillis) {
JLayer<J> jLayer = new JLayer<>(view);
final SwipeView<J> ui = new SwipeView<>(view, jLayer, durationMillis);
jLayer.setUI(ui);
return ui;
}
private final C liveComponent;
private Image priorScreen = null;
private Image upcomingScreen = null;
private final JLayer<C> layer;
private boolean isAnimating = false;
private SwipeDirection swipeDirection = SwipeDirection.SWIPE_RIGHT;
private final int maxFrames;
// Calculated:
#SuppressWarnings("FieldCanBeLocal")
private final int frameMillis;
private int frame = 0;
private final long startTime = System.currentTimeMillis();
private SwipeView(C view, JLayer<C> theLayer, int animationDurationMillis) {
super();
liveComponent = view;
layer = theLayer;
maxFrames = (30 * animationDurationMillis) / 1000;
frameMillis = animationDurationMillis / maxFrames;
}
public JLayer<C> getLayer() { return layer; }
/**
* Perform the specified operation with a swipe-right special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeRight(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-right after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeRight(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_RIGHT);
}
/**
* Perform the specified operation with a swipe-left special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-Left after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeLeft(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_LEFT);
}
private void swipe(Runnable operation, SwipeDirection swipeDirection) {
prepareToAnimate(swipeDirection);
operation.run();
animate();
}
// #SuppressWarnings({"HardCodedStringLiteral", "HardcodedFileSeparator"})
#Override
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth() * 2 * frame) / maxFrames;
if (swipeDirection == SwipeDirection.SWIPE_LEFT) {
xLimit = (c.getWidth() * 2) - xLimit;
}
int width = c.getWidth() * 2;
int height = c.getHeight() * 2;
// //noinspection UseOfSystemOutOrSystemErr
// System.out.printf("Dimensions: Frame: %d/%d (at %d) xLimit: %4d (%4d x %4d) (from %4d x %4d) Animating: %b%n",
// frame, maxFrames, System.currentTimeMillis() - startTime, xLimit, width, height, c.getWidth(), c.getHeight(), isAnimating);
assert upcomingScreen != null;
assert priorScreen != null;
Image pScreen = Objects.requireNonNull(priorScreen);
Image uScreen = Objects.requireNonNull(upcomingScreen);
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
}
} else {
super.paint(g, c);
}
}
private void prepareToAnimate(SwipeDirection swipeDirection) {
this.swipeDirection = swipeDirection;
isAnimating = true;
frame = 0;
// Save current state
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
}
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> {
frame++;
layer.repaint();
if (frame == maxFrames) {
frame = 0;
isAnimating = false;
timer.stop(); // Investigate: Am I leaking timers?
}
};
timer.addActionListener(actionListener);
upcomingScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
timer.start();
}
}
public static enum SwipeDirection {
#SuppressWarnings("JavaDoc") SWIPE_RIGHT,
#SuppressWarnings("JavaDoc") SWIPE_LEFT
}
}
I don't use a retina display, but I did notice a slight painting difference when the animation started.
I changed both of your BufferedImage to get rid of the alpha value and I no longer notice the painting difference:
//priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_RGB);
It turns out I needed to change the way I animated the frame to account for the doubling of the scale.
First, I needed to detect the scale. I added this code, which requires Java 9 or greater to work correctly. (It compiles under java 8, but fails to execute correctly, always returning 1 for any screen.)
private static final int SCALE = calculateScaleForDefaultScreen();
private static int calculateScaleForDefaultScreen() {
// scale will be 2.0 for a Retina screen, and 1.0 for an older screen
double scale = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getDefaultTransform()
.getScaleX(); // Requires Java 9+ to work. Compiles under Java 8 but always returns 1.0.
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(scale);
}
When I prepared my two off-screen graphics, I needed to do so at twice the scale:
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the current state of liveComponent into the image
graphics2D.dispose();
And…
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the upcoming state of liveComponent into the image
graphics2D.dispose();
Then, when I did my animation, I needed to include the SCALE in the drawing.
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
}
There are several other places where I multiplied widths and heights by 2. I changed those to SCALE as well.
I wish there were a more elegant solution, but this works.

javafx snapshot with slow performance

I am creating a basic space real time strategy game that needs a very large map, but will only show a small part of the map at a time. I have found that this is very easy to do with a viewport using imageview. Unfortunately, imageview needs an image to be passed to it and I have been using a canvas to draw all the sprites onto the background. I have been using snapshot to take an image of the fully rendered scene and passing that to imageview so I can use a viewport, but that has been causing a severe drop in performance. Without the use of snapshot, the game runs at 60+ fps, however when using snapshot the fps is always 15 or lower. Using snapshot was not a problem when the scene was 1024x512, but the scene is now 4096x2048. Is there a better way to use imageview and viewport 60 times a second without using snapshot? Example code of what I'm doing below:
Group trueRoot = new Group(); //shown image
Group root = new Group(); //invisible unedited image
Canvas canvas = new Canvas(4096, 2048); //unedited canvas
root.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
Scene scene2 = new Scene(trueRoot);
primaryStage.setScene(scene2);
WritableImage sceneImage = root.snapshot(new SnapshotParameters(), null); //snapshot of fully rendered root scene
ImageView imageView = new ImageView(sceneImage); //imageView can be used to resize or crop
Rectangle2D viewportRect = new Rectangle2D(0, 0, 1024, 512); //for cropping
imageView.setViewport(viewportRect); //for cropping
trueRoot.getChildren().add(imageView); //also for resizing / cropping
new AnimationTimer() {
public void handle(long currentNanoTime) {
//simple animated background
if (cloudTimer == 16384)
cloudTimer = 0;
else
cloudTimer++;
gc.drawImage(stars, 0, 0);
gc.drawImage(clouds1, cloudTimer % 16384, 0);
gc.drawImage(clouds1, (cloudTimer % 16384) - 8192, 0);
gc.drawImage(clouds2, (cloudTimer / 2) % 16384, 0);
gc.drawImage(clouds2, ((cloudTimer / 2) % 16384) - 8192, 0);
p1.update(p2, root);
p2.update(p1, root);
p1.render(gc, root);
p2.render(gc, root);
root.snapshot(new SnapshotParameters(), sceneImage); //takes root scene and hands it to trueRoot
}
}.start();
primaryStage.show();
full code at https://github.com/WiredOverload/ObliterateEverything
I know that I could use asynchronous snapshot to avoid delaying the rest of the code while the snapshot is taking place, but that would still leave the game at 15 fps. Any suggestions would be appreciated.
Just write your own viewport property with a listener that adjusts the GraphicsContext's transform to apply a transform & scale that fits the viewport to the Canvas's size:
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas();
GraphicsContext context = canvas.getGraphicsContext2D();
ObjectProperty<Rectangle2D> viewport = new SimpleObjectProperty<>(Rectangle2D.EMPTY);
Pane root = new Pane(canvas);
root.setPrefSize(400, 400);
canvas.setManaged(false);
root.layoutBoundsProperty().addListener((observable, oldBound, newBounds) -> {
canvas.setWidth(newBounds.getWidth());
canvas.setHeight(newBounds.getHeight());
Rectangle2D vp = viewport.get();
viewport.set(new Rectangle2D(vp.getMinX(), vp.getMinY(), newBounds.getWidth(), newBounds.getHeight()));
});
viewport.addListener((observable, oldViewport, newViewport) -> {
// combined transform & scale transfromation
context.setTransform(canvas.getWidth() / newViewport.getWidth(), 0,
0, canvas.getHeight() / newViewport.getHeight(),
-newViewport.getMinX(), -newViewport.getMinY());
redraw(context);
});
// sample movement animation
Transition scale = new Transition() {
{
setCycleDuration(Duration.seconds(2));
setCycleCount(2);
setAutoReverse(true);
}
#Override
protected void interpolate(double frac) {
double scale = 1 + 2 * frac;
Rectangle2D vp = viewport.get();
viewport.set(new Rectangle2D(vp.getMinX(), vp.getMinY(), canvas.getWidth() / scale, canvas.getHeight() / scale));
}
};
Transition move = new Transition() {
{
setCycleDuration(Duration.seconds(2));
setCycleCount(2);
setAutoReverse(true);
}
#Override
protected void interpolate(double frac) {
double posX = 300 * frac;
Rectangle2D vp = viewport.get();
viewport.set(new Rectangle2D(posX, 0, canvas.getWidth(), canvas.getHeight()));
}
};
SequentialTransition animation = new SequentialTransition(scale, move);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private static Color[] colors = new Color[]{
Color.RED,
Color.BEIGE,
Color.GREEN,
Color.ORANGE,
Color.BLACK,
Color.BLUE,
Color.WHEAT,
Color.CORAL,
Color.CRIMSON,
Color.PURPLE,
Color.AQUAMARINE,
Color.YELLOW,
Color.CHOCOLATE
};
public static void redraw(GraphicsContext gc) {
Bounds bounds;
try {
bounds = gc.getTransform().inverseTransform(gc.getCanvas().getLayoutBounds());
} catch (NonInvertibleTransformException ex) {
throw new IllegalStateException();
}
gc.clearRect(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
for (int i = 0; i < colors.length; i++) {
gc.setFill(colors[i]);
gc.fillRect(i * 50, 0, 50, 50);
}
}

Explicit positioning a Pane

I'm trying to create a basic UML diagramming tool with JavaFX.
First I did this:
public class Asd extends Application {
Double WINDOW_HEIGHT = (double) 400;
Double WINDOW_WIDTH = (double) 800;
Double RECTANGLE_DEFAULT_HEIGHT = (double) 50;
Double RECTANGLE_DEFAULT_WIDTH = (double) 100;
#Override
public void start(Stage stage) throws Exception {
final Pane root = new Pane();
Rectangle background = new Rectangle(WINDOW_WIDTH, WINDOW_HEIGHT,
Color.WHITE);
root.getChildren().add(background);
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
// On window click
root.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent mouseEvent) {
Rectangle rect = new Rectangle();
rect.setX(mouseEvent.getX());
rect.setY(mouseEvent.getY());
rect.setWidth(RECTANGLE_DEFAULT_WIDTH);
rect.setHeight(RECTANGLE_DEFAULT_HEIGHT);
rect.setFill(null);
rect.setStroke(Color.BLACK);
root.getChildren().add(rect);
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
With that, I can draw a rectangle (that would represent a UML Class) in the exact point I click.
Now I want to add text. If instead of
root.getChildren().add(rect);
I do
Text text = new Text("mockText");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(rect, text);
root.getChildren().add(stackPane);
the text is placed in the center of the rectangle, but all rectangles are positioned at coordinates (0,0)
So, any way to change the position of each StackPane? Or any alternative to add text to the rectangles directly without using StackPane?
OK, got it. Layout elements have methods setLayoutX & setLayoutY that can be used in cases like this one, so I don't need anymore Rectangle.setX/setY & finally the handle method should be:
public void handle(MouseEvent mouseEvent) {
Rectangle rect = new Rectangle();
rect.setWidth(RECTANGLE_DEFAULT_WIDTH);
rect.setHeight(RECTANGLE_DEFAULT_HEIGHT);
rect.setFill(null);
rect.setStroke(Color.BLACK);
Text text = new Text("mockText");
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(rect, text);
stackPane.setLayoutX(mouseEvent.getX());
stackPane.setLayoutY(mouseEvent.getY());
root.getChildren().add(stackPane);
}

Categories

Resources