Issue
Does anyone know why transparency drawing on a Canvas works perfectly fine using drawImage(), but doesn't work at all with a PixelWriter? I initially thought it may have something to do with Blend or some other mode/setting on the canvas/context, but haven't had any luck with that yet.
I need per-pixel variable transparency, not a single transparency value for the entire draw operation. I'll be rendering a number of "layers" (similar to how GIMP layers work, with optional transparency per-pixel). An additional open question is whether I'm better off first drawing the FINAL intended output to a WritableImage and then just drawing to the Canvas, for performance reasons, but that seems to defeat the point of using a Canvas in the first place...
Below is an example which shows a partially transparent Color being first drawn to an Image and then to the Canvas, and directly to the Canvas with setColor(). The transparent area is the Image draw, the opaque area is the setColor part. How do we get setColor() to respect Color alpha transparency for each pixel?
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
public class TransparencyTest extends Application {
private static final int width = 800;
private static final int height = 600;
private Scene scene;
private final Canvas canvas = new Canvas(width, height);
@Override
public void start(Stage stage) {
scene = new Scene(new Group(canvas));
stage.setScene(scene);
render();
stage.show();
exitOnEsc();
}
private void render() {
drawTransparentBg(canvas, 0, 0, width, height);
Color color = Color.web("#77000077");
WritableImage image = new WritableImage(200, 200);
for (int x = 0; x < 200; x++) {
for (int y = 0; y < 200; y++) {
image.getPixelWriter().setColor(x, y, color);
}
}
canvas.getGraphicsContext2D().drawImage(image, 50, 50);
for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
canvas.getGraphicsContext2D().getPixelWriter().setColor(x, y, color);
}
}
}
public void drawTransparentBg(Canvas canvas, int xPos, int yPos, int width, int height) {
int gridSize = 8;
boolean darkX = true;
String darkCol = "#111111";
String lightCol = "#222266";
for (int x = xPos; x < canvas.getWidth(); x += gridSize) {
boolean dark = darkX;
darkX = !darkX;
if (x > width) {
break;
}
for (int y = yPos; y < canvas.getHeight(); y += gridSize) {
if (y > height) {
break;
}
dark = !dark;
String color;
if (dark) {
color = darkCol;
} else {
color = lightCol;
}
canvas.getGraphicsContext2D().setFill(Paint.valueOf(color));
canvas.getGraphicsContext2D().fillRect(x, y, gridSize, gridSize);
}
}
}
private void exitOnEsc() {
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode().equals(KeyCode.ESCAPE)) {
Platform.exit();
}
});
}
}
Solution
The GraphicsContext
begins with the default BlendMode
, and all forms of drawImage()
use the current mode. In contrast, PixelWriter
methods replace values, ignoring the BlendMode
.
The example below lets you experiment with the supported BlendMode
values to see the effect. A related example is shown here,
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ChoiceBox;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TransparencyTest extends Application {
private static final int S = 8;
private static final int W = S * 36;
private static final int H = W;
private final Canvas canvas = new Canvas(W, H);
private final GraphicsContext g = canvas.getGraphicsContext2D();
private BlendMode mode = g.getGlobalBlendMode();
@Override
public void start(Stage stage) {
render(mode);
BorderPane root = new BorderPane(new Pane(canvas));
ObservableList<BlendMode> modes
= FXCollections.observableArrayList(BlendMode.values());
ChoiceBox<BlendMode> cb = new ChoiceBox<>(modes);
cb.setValue(mode);
cb.valueProperty().addListener((o) -> {
render(cb.getValue());
});
root.setBottom(cb);
stage.setScene(new Scene(root));
stage.show();
}
private void render(BlendMode mode) {
drawBackground();
g.setGlobalBlendMode(mode);
Color color = Color.web("#7f00007f");
int s = 24 * 8;
WritableImage image = new WritableImage(s, s);
for (int x = 0; x < s; x++) {
for (int y = 0; y < s; y++) {
image.getPixelWriter().setColor(x, y, color);
}
}
s = 6 * 8;
g.drawImage(image, s, s);
for (int x = 0; x < s; x++) {
for (int y = 0; y < s; y++) {
g.getPixelWriter().setColor(x, y, color);
}
}
}
public void drawBackground() {
g.setGlobalBlendMode(BlendMode.SRC_OVER);
Color darkCol = Color.web("#333333");
Color lightCol = Color.web("#cccccc");
boolean dark = false;
for (int x = 0; x < W; x += S) {
dark = !dark;
for (int y = 0; y < H; y += S) {
dark = !dark;
g.setFill(dark ? darkCol : lightCol);
g.fillRect(x, y, S, S);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
Answered By - trashgod