The program I am working on simply paints over text or images by using a canvas layer over a StackPane
. What I want to accomplish is that when I release the mouse the MouseEvent.MOUSE_RELEASED
handler it will automatically get a snapshot of the Canvas, add the image to an ImageView Cover
and display it on top of the TextArea
, but it cannot add the changes to the class StackPane
, namely the ImageView
.
What I have here is a program that I will add to another one I'm working on, and I plan to take everything from the main class, TextCanvas
, into the menu controller class from the main project.
Main class TextCanvas.java:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class TextCanvas extends Application
{
private ScrollPane Scroll = new ScrollPane();
Canvas Can = new Canvas(800, 400);
GraphicsContext GG = Can.getGraphicsContext2D();
TextArea TA = new TextArea();
ImageView Cover = new ImageView();
VBox ButtonBox = new VBox();
WordCanvas WC = new WordCanvas(Can, TA, GG);
@Override
public void start(Stage PrimaryStage)
{
ToggleGroup DrawErase = new ToggleGroup();
ToggleButton Draw = new ToggleButton("Draw");
ToggleButton Erase = new ToggleButton("Erase");
Button Clear = new Button("Clear");
Draw.setToggleGroup(DrawErase);
Erase.setToggleGroup(DrawErase);
double DotsPerInch = Screen.getPrimary().getDpi();
double W = DotsPerInch * 8.5;
double H = DotsPerInch * 11.0;
StackPane Stack = new WordCanvas(W, H);
WC.GetArea().setMaxWidth(W);
WC.GetArea().setMaxHeight(H);
WC.GetCan().setWidth(W);
WC.GetCan().setHeight(H);
DrawErase.selectedToggleProperty().addListener(new ChangeListener<Toggle>()
{
public void changed(ObservableValue<? extends Toggle> OV, Toggle TOld, Toggle TNew)
{
if(TNew == null)
{
GG.setStroke(Color.TRANSPARENT);
Stack.getChildren().remove(WC.GetCan());
}
else if(DrawErase.getSelectedToggle().equals(Draw))
{
GG.setStroke(Color.BLACK);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
else if(DrawErase.getSelectedToggle().equals(Erase))
{
GG.setStroke(Color.WHITE);
if(!Stack.getChildren().contains(WC.GetCan()))
{
Stack.getChildren().add(WC.GetCan());
}
}
}
});
Clear.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent e)
{
WC.GetGC().clearRect(0, 0, W, H);
Stack.getChildren().remove(WC.GetCover());
}
});
Button Snap = new Button("Snap");
Snap.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent e)
{
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(WC.GetCover()))
{
WC.GetCover().setImage(OutputImage);
Stack.getChildren().add(WC.GetCover());
}
else
{
WC.GetCover().setImage(OutputImage);
}
}
});
ButtonBox.getChildren().addAll(Draw, Erase, Clear, Snap);
BorderPane Border = new BorderPane();
Border.setCenter(Stack);
Border.setBottom(ButtonBox);
Scroll.setContent(Border);
Scroll.setFitToWidth(true);
Scroll.setFitToHeight(true);
Scene MainScene = new Scene(Scroll);
PrimaryStage.setMaximized(true);
PrimaryStage.setTitle("Practice Canvas");
PrimaryStage.setScene(MainScene);
PrimaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Secondary class that contains the TextArea and Canvas:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
public class WordCanvas extends StackPane
{
private Canvas Can = new Canvas();
private GraphicsContext GC = Can.getGraphicsContext2D();
private TextArea Area = new TextArea();
private ImageView Cover = new ImageView();
double Width;
double Height;
public WordCanvas()
{
CreateUI();
}
public WordCanvas(double W, double H)
{
this.Width = W;
this.Height = H;
CreateUI();
}
public WordCanvas(ImageView IV)
{
this.Cover = IV;
}
public WordCanvas(Canvas C, TextArea TA, GraphicsContext GG)
{
this.Can = C;
this.Area = TA;
this.GC = GG;
CreateUI();
}
public void CreateUI()
{
Caligraphy();
Imagination();
Color C = Color.STEELBLUE;
BackgroundFill BFill = new BackgroundFill(C, CornerRadii.EMPTY, Insets.EMPTY);
Background BGround = new Background(BFill);
this.getChildren().addAll(Area);
this.setBackground(BGround);
}
public void Caligraphy()
{
Area.setMaxWidth(Width);
Area.setMaxHeight(Height);
}
public void Imagination()
{
double CanvasWidth = GC.getCanvas().getWidth();
double CanvasHeight = GC.getCanvas().getHeight();
GC.setFill(Color.TRANSPARENT);
GC.fillRect(0, 0, Can.getWidth(), Can.getHeight());
GC.rect(0, 0, CanvasWidth, CanvasHeight);
GC.setLineWidth(3);
Can.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.CROSSHAIR);
}
});
Can.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
Can.setCursor(Cursor.DEFAULT);
}
});
Can.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
GC.beginPath();
GC.lineTo(event.getX(), event.getY());
GC.moveTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
GC.lineTo(event.getX(), event.getY());
GC.stroke();
}
});
Can.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
ImageView IV = new ImageView();
WordCanvas Stack = new WordCanvas(IV);
Cover.setMouseTransparent(true);
WritableImage WImage = Can.snapshot(new SnapshotParameters(), null);
ByteArrayOutputStream Bos = new ByteArrayOutputStream();
try
{
ImageIO.write(SwingFXUtils.fromFXImage(WImage, null), "png", Bos);
}
catch(IOException ex)
{
Logger.getLogger(WordCanvas.class.getName()).log(Level.SEVERE, null, ex);
}
InputStream Fis = new ByteArrayInputStream(Bos.toByteArray());
Image Imos = new Image(Fis);
int IW = (int) Imos.getWidth();
int IH = (int) Imos.getHeight();
WritableImage OutputImage = new WritableImage(IW, IH);
PixelReader PReader = Imos.getPixelReader();
PixelWriter PWriter = OutputImage.getPixelWriter();
for (int y = 0; y < IH; y++)
{
for (int x = 0; x < IW; x++)
{
int argb = PReader.getArgb(x, y);
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
if(r >= 0xCF && g >= 0xCF && b >= 0xCF)
{
argb &= 0x00FFFFFF;
}
PWriter.setArgb(x, y, argb);
}
}
if(!Stack.getChildren().contains(Cover))
{
Cover.setImage(OutputImage);
Stack.getChildren().add(Cover);
}
else
{
Cover.setImage(OutputImage);
}
}
});
}
public void SetCan(Canvas C)
{
this.Can = C;
}
public Canvas GetCan()
{
return Can;
}
public void SetGC(GraphicsContext GG)
{
this.GC = GG;
}
public GraphicsContext GetGC()
{
return GC;
}
public void SetArea(TextArea TA)
{
this.Area = TA;
}
public TextArea GetArea()
{
return Area;
}
public ImageView GetCover()
{
return Cover;
}
}
In the main class the Snap button handler does work as I intend to, but what I want is that in the secondary class the MouseEvent.MOUSE_RELEASED
even handler automatically creates the snapshot and does what the Snap button in the main class does. However nothing I have tried works, and it won't even accept this.getChildren().add(Cover)
.
Another minor problem, I want the WritableImage
to automatically become transparent with a more elegant solution. User @jewelsea gives a solution here which works perfectly but I would prefer something a bit shorter that doesn't have to read through every pixel. Existing png files do work as intended but when I make my own png files they are not transparent.