I am currently trying to create an image from a TableView using snapshot, and the snapshot is only grabbing the tableview that is visible on the screen. I've tried messing with the SnapshotPreferencesviewport, but that didn't help. Any suggestions or workarounds would be appreciated.
EDIT: I have now also tried creating a new pane of the same width and height of the TableView, adding the TableView, and taking a snapshot of that, but it ends up blank. I've continued to play around with this. Creating a new pane and adding it to a scene gives me all the columns of the TableView, but not all the rows.
Code to take the snapshot is below.
WritableImage image = null;
//Get the node
TableView<ObservableList<ObjectProperty<Item>>>pattern = mainApp.getPVController().getPattern();
//Create rectangle to define viewport
Rectangle2D rect = new Rectangle2D(0, 0, pattern.getWidth(), pattern.getHeight());
//Define snapshot parameters
SnapshotParameters params = new SnapshotParameters();
params.setViewport(rect);
//Take the snapshot
image = pattern.snapshot(params, image);
Ok, so I figured out a workaround. I used the underlying data of the TableView and made a GridPane that represented the underlying data, which worked well. My data was represented by color, so that is what you will see below. You could certainly do this with other data types.
Now I am having trouble with the gridlines that I put in varying their shade and width- but that's another question. Code to create the GridPane is below.
GridPane gPane = new GridPane();
gPane.setSnapToPixel(true);
gPane.setStyle("-fx-background-color: DARKGREY; -fx-padding: 1;"
+"-fx-hgap: 1; -fx-vgap: 1;");
for(int i=0; i<mainApp.getItemList().size(); i++){ // rows
for(int j=0; j<mainApp.getItemList().get(0).size(); j++){ //columns
Color color = mainApp.getItemList().get(i).get(j).getValue().getDisplayColor();
int r = (int) (color.getRed() * 255);
int g = (int) (color.getGreen() * 255);
int b = (int) (color.getBlue() * 255);
Rectangle rect = new Rectangle(5,5);
rect.setStyle("-fx-fill: rgb(" + r + "," + g + "," + b + ");");
gPane.add(rect, j, i);
}
}
Scene scene = new Scene(gPane);
WritableImage image = gPane.snapshot(new SnapshotParameters(), null);
Related
I am clearly missing an important concept here. I have written code using mouse events to draw a boundary (a polygon) on an existing BufferedImage. Here is the relevant section:
public void paintComponent(Graphics g)
{
super.paintComponent(g); //Paint parent's background
//G3 displays the BufferedImage "Drawing" with each paint
Graphics2D G3 = (Graphics2D)g;
G3.drawImage(this.Drawing, 0, 0, null);
G3.dispose();
}
public void updateDrawing()
{
int x0, y0, x1, y1; // Vertex coordinates
Line2D.Float seg;
// grafix is painting the mouse drawing to the BufferedImage "Drawing"
if(this.pts.size() > 0)
{
for(int ip = 0; ip < pts.size(); ip++)
{
x0 = (int)this.pts.get(ip).x;
y0 = (int)this.pts.get(ip).y;
this.grafix.drawRect(x0 - this.sqw/2, y0 - this.sqh/2, + this.sqw, this.sqh);
if (ip > 0)
{
x1 = (int)this.pts.get(ip-1).x;
y1 = (int)this.pts.get(ip-1).y;
this.grafix.drawLine(x1, y1, x0, y0);
seg = new Line2D.Float(x1, y1, x0, y0);
this.segments.add(seg);
}
}
}
repaint();
}
The next two routines are called by the mouse events: Left click gets the next point and right click closes the region.
public void getNextPoint(Point2D p)
{
this.isDrawing = true;
Point2D.Float next = new Point2D.Float();
next.x = (float) p.getX();
next.y = (float) p.getY();
this.pts.add(next);
updateDrawing();
}
public void closeBoundary()
{
//Connects the last point to the first point to close the loop
Point2D.Float next = new Point2D.Float(this.pts.get(0).x, this.pts.get(0).y);
this.pts.add(next);
this.isDrawing = false;
updateDrawing();
}
It all works fine and I can save the image with my drawing on it:
image with drawing
The list of vertices (pts) and the line segments (segments) are all that describe the region/shape/polygon.
I wish to extract from the original image only that region enclosed within the boundary. That is, I plan to create a new BufferedImage by moving through all of the pixels, testing to see if they fall within the figure and keep them if they do.
So I want to create an AREA from the points and segments I've collected in drawing the shape. Everything says: create an AREA variable and "getPathIterator". But on what shape? My AREA variable will be empty. How does the path iterator access the points in my list?
I've been all over the literature and this website as well.
I'm missing something.
Thank you haraldK for your suggestion. Before I saw your post, I came to a similar conclusion:
Using the Arraylist of vertices from the paint operation, I populated a "Path2D.Float" object called "contour" by looping through the points list that was created during the "painting" operation. Using this "contour" object, I instantiated an Area called "interferogram". Just to check my work, I created another PathIterator, "PI", from the Area and decomposed the Area, "interferogram" into "segments" sending the results to the console. I show the code below:
private void mnuitmKeepInsideActionPerformed(java.awt.event.ActionEvent evt)
{
// Keeps the inner area of interest
// Vertices is the "pts" list from Class MouseDrawing (mask)
// It is already a closed path
ArrayList<Point2D.Float> vertices =
new ArrayList<>(this.mask.getVertices());
this.contour = new Path2D.Float(Path2D.WIND_NON_ZERO);
// Read the vertices into the Path2D variable "contour"
this.contour.moveTo((float)vertices.get(0).getX(),
(float)vertices.get(0).getY()); //Starting location
for(int ivertex = 1; ivertex < vertices.size(); ivertex++)
{
this.contour.lineTo((float)vertices.get(ivertex).getX(),
(float)vertices.get(ivertex).getY());
}
this.interferogram = new Area(this.contour);
PathIterator PI = this.interferogram.getPathIterator(null);
//Test print out the segment types and vertices for debug
float[] p = new float[6];
int icount = 0;
while( !PI.isDone())
{
int type = PI.currentSegment(p);
System.out.print(icount);
System.out.print(" Type " + type);
System.out.print(" X " + p[0]);
System.out.println(" Y " + p[1]);
icount++;
PI.next();
}
BufferedImage masked = Mask(this.image_out, this.interferogram);
// Write image to file for debug
String dir;
dir = System.getProperty("user.dir");
dir = dir + "\\00masked.png";
writeImage(masked, dir, "PNG");
}
Next, I applied the mask to the image testing each pixel for inclusion in the area using the code below:
public BufferedImage Mask(BufferedImage BIM, Area area)
{
/** Loop through the pixels in the image and test each one for inclusion
* within the area.
* Change the colors of those outside
**/
Point2D p = new Point2D.Double(0,0);
// rgb should be white
int rgb = (255 << 24);
for (int row = 0; row < BIM.getWidth(); row++)
{
for (int col = 0; col < BIM.getHeight(); col++)
{
p.setLocation(col, row);
if(!area.contains(p))
{
BIM.setRGB(col, row, rgb);
}
}
}
return BIM;
}
public static BufferedImage deepCopy(BufferedImage B2M)
{
ColorModel cm = B2M.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = B2M.copyData(B2M.getRaster()
.createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
This worked beautifully (I was surprised!) except for one slight detail: the lines of the area appeared around the outside of the masked image.
In order to remedy this, I copied the original (resized) image before the painting operation. Many thanks to user1050755 (Nov 2014) for the routine deepCopy that I found on this website. Applying my mask to the copied image resulted in the portion of the original image I wanted without the mask lines. The result is shown in the attached picture. I am stoked!
masked image
I haven't experience with processing images in java. My goal is to combine several images. To be more detail, I have a template image and some other images. I want to put those images into template image at specific places.
For e.g:
template image:
specific image:
So, I want to put the dog image onto cats' image places and store the created image.
Please, tell me what is the easiest way to do that?
As Fabian pointed out, identifying patterns mightn't give the expected results, so my suggestion would be an alternative
If you control the templates and provide them to the user as options, you could implement them yourself and populate the images in placeholder nodes. The merged image would come from taking an overall snapshot
I've included a quick example, but note that it's not fully implemented (layout etc) so consider it more as a proof of concept. It's still possible to build on the below to display different images at the same time, text decoration, stars etc to be a closer representation of the example image you provided
This may not be the easiest method, but it could be an enjoyable learning experience. This could also be a viable option since you don't have image processing experience in Java
public class ImageTemplateNode extends Region{
private SimpleObjectProperty<Image> displayedImageProperty;
private ObservableList<Node> children = FXCollections.observableArrayList();
private Random random = new Random();
private int rows, columns;
private final int maximumRotation = 15;
public ImageTemplateNode(int rows, int cols, Image imageToDisplay){
this.rows = rows;
this.columns = cols;
this.displayedImageProperty = new SimpleObjectProperty<>(imageToDisplay);
createDisplayNodes();
setPadding(new Insets(10));
Bindings.bindContentBidirectional(getChildren(), children);
}
public ImageTemplateNode(int rows, int cols, Image imageToDisplay, Image backgroundImage){
this(rows, cols, imageToDisplay);
setBackgroundImage(backgroundImage);
}
private void createDisplayNodes(){
for(int count = 0; count < (rows * columns); count++){
StackPane container = new StackPane();
container.setRotate(getRandomRotationValue());
container.setBackground(
new Background(new BackgroundFill(getRandomColour(), new CornerRadii(5), new Insets(5))));
container.maxWidthProperty().bind(displayedImageProperty.get().widthProperty().add(25));
container.maxHeightProperty().bind(displayedImageProperty.get().heightProperty().add(25));
ImageView displayNode = new ImageView();
displayNode.imageProperty().bind(displayedImageProperty);
displayNode.fitWidthProperty().bind(container.widthProperty().subtract(25));
displayNode.fitHeightProperty().bind(container.heightProperty().subtract(25));
container.getChildren().setAll(displayNode);
children.add(container);
}
}
private int getRandomRotationValue(){
int randomValue = random.nextInt(maximumRotation);
//Rotate clockwise if even, anti-clockwise if odd
return randomValue % 2 == 0 ? randomValue : 360 - randomValue;
}
private Color getRandomColour(){
int red = random.nextInt(256);
int green = random.nextInt(256);
int blue = random.nextInt(256);
return Color.rgb(red, green, blue);
}
#Override
protected void layoutChildren() {
//Calculate the dimensions for the children so that they do not breach the padding and allow for rotation
double cellWidth = (widthProperty().doubleValue()
- getPadding().getLeft() - getPadding().getRight() - maximumRotation) / columns;
double cellHeight = (heightProperty().doubleValue()
- getPadding().getTop() - getPadding().getBottom() - maximumRotation) / rows;
for (int i = 0; i < (rows); i++) {
for (int j = 0; j < (columns); j++) {
if (children.size() <= ((i * (columns)) + j)) {
break;
}
Node childNode = children.get((i * (columns)) + j);
layoutInArea(childNode,
(j * cellWidth) + getPadding().getLeft(),
(i * cellHeight) + getPadding().getTop(), cellWidth, cellHeight,
0.0d, HPos.CENTER, VPos.CENTER);
}
}
}
public void setBackgroundImage(Image backgroundImage){
setBackground(new Background(
new BackgroundImage(backgroundImage,
BackgroundRepeat.REPEAT, BackgroundRepeat.REPEAT, BackgroundPosition.CENTER,
BackgroundSize.DEFAULT)));
}
public void changeDisplayImage(Image newImageToDisplay){
displayedImageProperty.set(newImageToDisplay);
}
public void captureAndSaveDisplay(){
FileChooser fileChooser = new FileChooser();
//Set extension filter
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("png files (*.png)", "*.png"));
//Prompt user to select a file
File file = fileChooser.showSaveDialog(null);
if(file != null){
try {
//Pad the capture area
WritableImage writableImage = new WritableImage((int)getWidth() + 20,
(int)getHeight() + 20);
snapshot(null, writableImage);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(writableImage, null);
//Write the snapshot to the chosen file
ImageIO.write(renderedImage, "png", file);
} catch (IOException ex) { ex.printStackTrace(); }
}
}
}
Screen shots:
Saved snap shots:
I am attempting to create a clock in JavaFX, everything worked out well, except for the numbers which represent the time (from 1 to 12).
I have this piece of code:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i));
//center it
label.setTranslateX(100);
label.setTranslateY(100);
label.getTransforms().add(new Rotate(i * (360 / 12)));
//rotate it.
numbers.getChildren().add(label);
}
This doesn't work, the numbers are just rotated in the center, but i want them to move outside (to the edge of the outer circle, like a normal clock.
Can someone help me?
Thank you very much.
If you combine Translate with Rotate transforms:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i==0?12:i));
label.getTransforms().add(new Rotate(i * (360d / 12d)));
label.getTransforms().add(new Translate(100d,100d));
numbers.getChildren().add(label);
}
You will have a 'rotated' clock:
But as you can see, you are rotating your labels, not their position.
One way to approach this is finding the position of each label by using a small Circle, rotating it to its final position and then moving the label:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i==0?12:i));
Circle c=new Circle(1);
c.getTransforms().add(new Rotate(i * (360d / 12d)));
c.getTransforms().add(new Translate(0,-100));
label.setTranslateX(c.localToParent(0,0).getX());
label.setTranslateY(c.localToParent(0,0).getY());
numbers.getChildren().addAll(c,label);
}
Note I've adjusted the translation to start right at the 12 hour position.
You will notice the labels are located down and right from their circle, so you should move them accordingly.
EDIT
To avoid the need of fixing the labels position, you can use a StackPane to wrap each pair of circles and labels:
for(int i = 0; i < 12; i++){
Label label = new Label(String.valueOf(i==0?12:i));
Circle c=new Circle(1);
c.getTransforms().add(new Rotate((i) * (360d / 12d)));
c.getTransforms().add(new Translate(0,-100d));
label.setTranslateX(c.localToParent(0,0).getX());
label.setTranslateY(c.localToParent(0,0).getY());
StackPane sp = new StackPane(c,label);
numbers.getChildren().add(sp);
}
Finally, have a look at this question, maybe it's easier just using a circular pane.
I have AnchorPane which is the Main that has all other panes.
Inside it I have the board area which is AnchorPane and area for the dice which is also AnchorPane.
This is how it looks on scene builder:
inside the board Area which is AnchorPane I am creating dynamically (according to the user request) the size of the board (5x5, 6x6, 7x7 or 8x8):
GridPane boardGame;
#FXML
Button dice;
#FXML
AnchorPane boardArea;
#FXML
AnchorPane boardGameAnchorPane;
#FXML
AnchorPane dicePane;
public void CreateBoard()
{
int boardSize = m_Engine.GetBoard().GetBoardSize();
int num = 1;
int maxColumns = m_Engine.GetNumOfCols();
int maxRows = m_Engine.GetNumOfRows();
boardGame = new GridPane();
//boardGame.setAlignment(Pos.CENTER);
for(int row = maxRows - 1; row >= 0 ; row--)
{
for(int col = 0; col < maxColumns ; col++)
{
StackPane stackPane = new StackPane();
stackPane.setMaxSize(SIZE_OF_CELL, SIZE_OF_CELL);
stackPane.setMinSize(SIZE_OF_CELL, SIZE_OF_CELL);
if((col + row) % 2 != 0)
{
stackPane.getStyleClass().add("oddCellBorder");
}
else
{
stackPane.getStyleClass().add("evenCellBorder");
}
Label label = new Label(String.valueOf(num));
StackPane.setAlignment(label, Pos.BOTTOM_LEFT);
stackPane.getChildren().add(label);
boardGame.add(stackPane, col, row);
num++;
}
}
this.fixBoardGameSize();
boardGame.setGridLinesVisible(true);
// boardGame.autosize();
boardArea.getChildren().add(boardGame);
//ImageView imageView = ImageUtils.getImageView("diceTransprntBack3D.png");
//dice.setGraphic(imageView);
// Image img1 = new Image(getClass().getResourceAsStream("about.png"));
}
My problem starts from board size of 7x7 or 8x8.
The board area spread to the dice area:
I tried to fix the sizes manually with this function:
private void fixBoardGameSize()
{
boardArea.setMinHeight(boardGame.getHeight());
boardArea.setMaxHeight(boardGame.getHeight());
boardArea.setMinWidth(boardGame.getWidth());
boardArea.setMaxWidth(boardGame.getWidth());
boardGameAnchorPane.setPrefWidth(boardArea.getWidth() + dicePane.getWidth() + 500 );
boardGameAnchorPane.setMinWidth(boardArea.getWidth() + dicePane.getWidth() + 500 );
boardGameAnchorPane.setMaxWidth(boardArea.getWidth() + dicePane.getWidth() + 500 );
dicePane.setLayoutX(boardArea.getLayoutX() + boardArea.getWidth() + 1000);
}
But with no success.
I tried also to play with the anchor pain constraint with no success.
I searched but can't find something that can help me so I am asking here if someone knows how I can keep the two anchor panes separated.
Based on your description, using the same anchor panes, and given a fixed size of your application, what you can do is:
First, set a maximum size for the boardArea, a margin for the grid to be correctly displayed on that pane
// Maximum fixed size you give to boardArea
public static final double MAX_SIZE = 600;
// Margin to border
public static final double MARGIN = 25;
And now, in your fixBoardGameSize() method, resize properly the grid:
private void fixBoardGameSize(){
// Listener, since boardGame dimensions are determined after the stage is shown
ChangeListener<Number> resize = (ov, v, v1) -> {
double scale = Math.min((MAX_SIZE - 2d*MARGIN) / boardGame.getWidth(),
(MAX_SIZE - 2d*MARGIN) / boardGame.getHeight());
boardGame.setScaleX(scale);
boardGame.setScaleY(scale);
boardGame.setTranslateX((MAX_SIZE - boardGame.getWidth()) / 2d);
boardGame.setTranslateY((MAX_SIZE - boardGame.getHeight()) / 2d);
};
boardGame.widthProperty().addListener(resize);
boardGame.heightProperty().addListener(resize);
}
Now you can display games with any number of rows and columns. It will always fit in the board, and it will be properly centered.
(Sorry for the links, I'm new and I cannot post images)
I want to accomplish the following : create a table with the legends on the top, and in a diagonal way.
but I'm having some problems, I have the following image, and I'm trying to rotate it 45º (the result it's at the right),
Here is my code:
//just some labels
ArrayList<String> labels = new ArrayList<String>();
labels.add("Juan");
labels.add("QWERTYYY");
labels.add("ANA");
// margin
int margin=3;
//diagonal = 45º
// value to shift each label
int diagonalShift = (int)(cellSizeWidth / Math.sqrt(2d));
// height, width represent the size of the final image
// heightSub, widthSub represent the size of the image to be rotated taking into account the shift for each label
int widthSub = height + (diagonalShift * labels.size());
int heightSub = width;
// image to Display
BufferedImage image = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
Graphics2D imageGraphics = (Graphics2D) image.getGraphics();
// tempImage: subImage to rotate and place in image
BufferedImage tempImage = new BufferedImage(widthSub, heightSub, BufferedImage.TYPE_INT_RGB);
Graphics2D tempImageGraphics = (Graphics2D) tempImage.getGraphics();
tempImageGraphics.setColor(Color.BLUE);
tempImageGraphics.drawRect(0, 0, widthSub-1, heightSub-1);
// I'd like to use antialias, but it's giving bad results
// tempImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
// drawing labels
// as we're designing a table cellSizeWidth and CellSizeHeight represent the dimensions for each cell
tempImageGraphics.setColor(Color.WHITE);
for (int i = 0; i < labels.size(); i++) {
String label = labels.get(i);
tempImageGraphics.drawString(label,
margin + (i * diagonalShift),
(int) (i * cellSizeWidth) + fontSize + centerDistance);
}
I tried the following:
//rotating
AffineTransform fontAfineTransform = new AffineTransform();
// fontAfineTransform.rotate(verticalTextDirection.rotationAngle());
which gives as result the image at the right in the second Image 2
so I need to apply a translation to get it to the right position
// Math.sqrt(2d) because I'm working with 45º and the height becomes the hypotenuse
// fontAfineTransform.translate(-height/Math.sqrt(2d),height/Math.sqrt(2d));
//drawing into image
imageGraphics.drawImage(tempImage, fontAfineTransform, null);
can someone please explain how the affineTransform works, or how can I get the text to be in a diagonal way.
Thanks