JavaFX 渲染/图像处理
JavaFX rendering / image manipulation
我制作了一个生成长阴影的 JavaFX 小应用程序。在这一点上,我在渲染上苦苦挣扎(见图)。
- 矩形角上缺失的线似乎很难修复。更改应用操作的循环会弄乱其他形状的阴影(例如圆形)。
- 我猜 'a' 的故障与 Bresenham algorithm 有关。(?)
附加信息:
更改图像分辨率没有任何区别:故障不断出现。
问题:
如何修复? SDK 是否提供有用的东西?我必须重写代码吗?
代码
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
public class Main extends Application {
private PrintWriter writer;
private String colorObjFilter = "0x009688ff";
private static final String IMG_PATH = "img/ls-test-1k.png";
private static final int LONGSHADOW_LENGTH = 100;
private static final String
ANSI_GREEN = "\u001B[32m",
ANSI_RESET = "\u001B[0m";
@Override
public void start(Stage stage) throws Exception {
writer = new PrintWriter("out.txt", "UTF-8");
InputStream is = new FileInputStream(new File(IMG_PATH));
ImageView imageView = new ImageView(new Image(is));
Image image = imageView.getImage();
StackPane root = new StackPane();
Scene scene = new Scene(root, image.getWidth(), image.getHeight(), Paint.valueOf
("#EEEEEE"));
scene.addEventFilter(KeyEvent.KEY_PRESSED, evt -> {
if (evt.getCode().equals(KeyCode.ESCAPE)) {
stage.close();
}
});
final Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
canvas.setOnMouseClicked((MouseEvent e) -> {
Color color = image.getPixelReader().getColor((int) e.getX(), (int) e.getY());
System.out.println(ANSI_GREEN + " -> " + color.toString() + ANSI_RESET);
colorObjFilter = color.toString();
try {
processImage(root, canvas, image);
} catch (IOException e1) {
e1.printStackTrace();
}
});
root.getChildren().addAll(imageView, canvas);
stage.setScene(scene);
stage.show();
}
private void processImage(StackPane root, Canvas canvas, Image image) throws IOException {
long delta = System.currentTimeMillis();
int width = (int) image.getWidth();
int height = (int) image.getHeight();
GraphicsContext gc = canvas.getGraphicsContext2D();
System.out.println("width: " + width + "\theight: " + height);
BufferedImage bufferedImage = ImageIO.read(new File(IMG_PATH));
// keep threshold small to get clean paths to draw
edgeDetection(gc, image, 0.00000001d);
writer.close();
Label label = new Label();
root.setAlignment(Pos.BOTTOM_LEFT);
root.setOnMouseMoved(event -> label.setText(event.getX() + "|" + event.getY()
+ "|" + bufferedImage.getRGB((int) event.getX(), (int) event.getY())));
root.getChildren().addAll(label);
System.out.println("took: " + (System.currentTimeMillis() - delta) + " ms");
}
public void edgeDetection(GraphicsContext gc, Image image, double threshold) {
Color topPxl, lowerPxl;
double topIntensity, lowerIntensity;
PixelWriter pw = gc.getPixelWriter();
for (int y = 0; y < image.getHeight() - 1; y++) {
for (int x = 1; x < image.getWidth(); x++) {
topPxl = image.getPixelReader().getColor(x, y);
lowerPxl = image.getPixelReader().getColor(x - 1, y + 1);
topIntensity = (topPxl.getRed() + topPxl.getGreen() + topPxl.getBlue()) / 3;
lowerIntensity = (lowerPxl.getRed() + lowerPxl.getGreen() + lowerPxl.getBlue()) / 3;
if (Math.abs(topIntensity - lowerIntensity) > threshold) {
int y2 = y;
for (int x2 = x; x2 < x + LONGSHADOW_LENGTH; x2++) {
y2++;
try {
Color color = image.getPixelReader().getColor(x2, y2);
// colorObjFilter protects the purple letter being manipulated
if (!color.toString().toLowerCase()
.contains(colorObjFilter.toLowerCase())) {
pw.setColor(x2, y2, Color.color(.7f, .7f, .7f, .9f));
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
我不知道为什么你的原始程序有一些渲染瑕疵。
这是一个替代解决方案,它非常暴力,因为它只是生成一个阴影图像,它以不同的偏移量一遍又一遍地渲染,最终得到一个长长的阴影。演示了几种生成 shadowImage 的方法,一种是 ColorAdjust effect on the original image, the other is generation of a shadow image using a PixelWriter。
您最初的解决方案是使用 PixelWriter 为所有事物使用适当的阴影生成算法,这更优雅(如果您能让它工作 ;-)。
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ShadowSpray extends Application {
private static final double W = 400;
private static final double H = 400;
private static final int SHADOW_LENGTH = 100;
private static final double IMG_X = 20;
private static final double IMG_Y = 20;
private static final int FONT_SIZE = 200;
private static final double SHADOW_SLOPE_FACTOR = 1.5;
Color SHADOW_COLOR = Color.GRAY.brighter();
@Override
public void start(Stage stage) {
Image image = getImage();
Canvas canvas = new Canvas(W, H);
GraphicsContext gc = canvas.getGraphicsContext2D();
// drawWithShadowUsingStencil(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH, SHADOW_COLOR);
drawWithShadowUsingColorAdjust(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH);
stage.setScene(new Scene(new Group(canvas)));
stage.show();
}
private void drawWithShadowUsingColorAdjust(GraphicsContext gc, Image image, double x, double y, int shadowLength) {
// here the color adjust for the shadow is based upon the intensity of the input image color
// which is a weird way to calculate a shadow color, but does come out nicely
// because it appropriately handles antialiased input images.
ColorAdjust monochrome = new ColorAdjust();
monochrome.setBrightness(+0.5);
monochrome.setSaturation(-1.0);
gc.setEffect(monochrome);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(image, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.setEffect(null);
gc.drawImage(image, x, y);
}
private void drawWithShadowUsingStencil(GraphicsContext gc, Image image, double x, double y, int shadowLength, Color shadowColor) {
Image shadow = createShadowImage(image, shadowColor);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(shadow, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.drawImage(image, x, y);
}
private Image createShadowImage(Image image, Color shadowColor) {
WritableImage shadow = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
PixelReader reader = shadow.getPixelReader();
PixelWriter writer = shadow.getPixelWriter();
for (int ix = 0; ix < image.getWidth(); ix++) {
for (int iy = 0; iy < image.getHeight(); iy++) {
int argb = reader.getArgb(ix, iy);
int a = (argb >> 24) & 0xFF;
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
// because we use a binary choice, we lose anti-alising info in the shadow so it looks a bit jaggy.
Color fill = (r > 0 || g > 0 || b > 0) ? shadowColor : Color.TRANSPARENT;
writer.setColor(ix, iy, fill);
}
}
return shadow;
}
private Image getImage() {
Label label = new Label("a");
label.setStyle("-fx-text-fill: forestgreen; -fx-background-color: transparent; -fx-font-size: " + FONT_SIZE + "px;");
Scene scene = new Scene(label, Color.TRANSPARENT);
SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
return label.snapshot(snapshotParameters, null);
}
public static void main(String[] args) {
launch();
}
}
内置的JavaFX有一个DropShadow效果,这几乎就是你想要的,尤其是当你将spread设置为1,radius设置为0时,然而,它只是生成一个单独的偏移阴影图像,而不是长阴影效果。
有一些替代文本和更短的 "long shadow":
我制作了一个生成长阴影的 JavaFX 小应用程序。在这一点上,我在渲染上苦苦挣扎(见图)。
- 矩形角上缺失的线似乎很难修复。更改应用操作的循环会弄乱其他形状的阴影(例如圆形)。
- 我猜 'a' 的故障与 Bresenham algorithm 有关。(?)
附加信息:
更改图像分辨率没有任何区别:故障不断出现。
问题:
如何修复? SDK 是否提供有用的东西?我必须重写代码吗?
代码
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
public class Main extends Application {
private PrintWriter writer;
private String colorObjFilter = "0x009688ff";
private static final String IMG_PATH = "img/ls-test-1k.png";
private static final int LONGSHADOW_LENGTH = 100;
private static final String
ANSI_GREEN = "\u001B[32m",
ANSI_RESET = "\u001B[0m";
@Override
public void start(Stage stage) throws Exception {
writer = new PrintWriter("out.txt", "UTF-8");
InputStream is = new FileInputStream(new File(IMG_PATH));
ImageView imageView = new ImageView(new Image(is));
Image image = imageView.getImage();
StackPane root = new StackPane();
Scene scene = new Scene(root, image.getWidth(), image.getHeight(), Paint.valueOf
("#EEEEEE"));
scene.addEventFilter(KeyEvent.KEY_PRESSED, evt -> {
if (evt.getCode().equals(KeyCode.ESCAPE)) {
stage.close();
}
});
final Canvas canvas = new Canvas(image.getWidth(), image.getHeight());
canvas.setOnMouseClicked((MouseEvent e) -> {
Color color = image.getPixelReader().getColor((int) e.getX(), (int) e.getY());
System.out.println(ANSI_GREEN + " -> " + color.toString() + ANSI_RESET);
colorObjFilter = color.toString();
try {
processImage(root, canvas, image);
} catch (IOException e1) {
e1.printStackTrace();
}
});
root.getChildren().addAll(imageView, canvas);
stage.setScene(scene);
stage.show();
}
private void processImage(StackPane root, Canvas canvas, Image image) throws IOException {
long delta = System.currentTimeMillis();
int width = (int) image.getWidth();
int height = (int) image.getHeight();
GraphicsContext gc = canvas.getGraphicsContext2D();
System.out.println("width: " + width + "\theight: " + height);
BufferedImage bufferedImage = ImageIO.read(new File(IMG_PATH));
// keep threshold small to get clean paths to draw
edgeDetection(gc, image, 0.00000001d);
writer.close();
Label label = new Label();
root.setAlignment(Pos.BOTTOM_LEFT);
root.setOnMouseMoved(event -> label.setText(event.getX() + "|" + event.getY()
+ "|" + bufferedImage.getRGB((int) event.getX(), (int) event.getY())));
root.getChildren().addAll(label);
System.out.println("took: " + (System.currentTimeMillis() - delta) + " ms");
}
public void edgeDetection(GraphicsContext gc, Image image, double threshold) {
Color topPxl, lowerPxl;
double topIntensity, lowerIntensity;
PixelWriter pw = gc.getPixelWriter();
for (int y = 0; y < image.getHeight() - 1; y++) {
for (int x = 1; x < image.getWidth(); x++) {
topPxl = image.getPixelReader().getColor(x, y);
lowerPxl = image.getPixelReader().getColor(x - 1, y + 1);
topIntensity = (topPxl.getRed() + topPxl.getGreen() + topPxl.getBlue()) / 3;
lowerIntensity = (lowerPxl.getRed() + lowerPxl.getGreen() + lowerPxl.getBlue()) / 3;
if (Math.abs(topIntensity - lowerIntensity) > threshold) {
int y2 = y;
for (int x2 = x; x2 < x + LONGSHADOW_LENGTH; x2++) {
y2++;
try {
Color color = image.getPixelReader().getColor(x2, y2);
// colorObjFilter protects the purple letter being manipulated
if (!color.toString().toLowerCase()
.contains(colorObjFilter.toLowerCase())) {
pw.setColor(x2, y2, Color.color(.7f, .7f, .7f, .9f));
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
我不知道为什么你的原始程序有一些渲染瑕疵。
这是一个替代解决方案,它非常暴力,因为它只是生成一个阴影图像,它以不同的偏移量一遍又一遍地渲染,最终得到一个长长的阴影。演示了几种生成 shadowImage 的方法,一种是 ColorAdjust effect on the original image, the other is generation of a shadow image using a PixelWriter。
您最初的解决方案是使用 PixelWriter 为所有事物使用适当的阴影生成算法,这更优雅(如果您能让它工作 ;-)。
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ShadowSpray extends Application {
private static final double W = 400;
private static final double H = 400;
private static final int SHADOW_LENGTH = 100;
private static final double IMG_X = 20;
private static final double IMG_Y = 20;
private static final int FONT_SIZE = 200;
private static final double SHADOW_SLOPE_FACTOR = 1.5;
Color SHADOW_COLOR = Color.GRAY.brighter();
@Override
public void start(Stage stage) {
Image image = getImage();
Canvas canvas = new Canvas(W, H);
GraphicsContext gc = canvas.getGraphicsContext2D();
// drawWithShadowUsingStencil(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH, SHADOW_COLOR);
drawWithShadowUsingColorAdjust(gc, image, IMG_X, IMG_Y, SHADOW_LENGTH);
stage.setScene(new Scene(new Group(canvas)));
stage.show();
}
private void drawWithShadowUsingColorAdjust(GraphicsContext gc, Image image, double x, double y, int shadowLength) {
// here the color adjust for the shadow is based upon the intensity of the input image color
// which is a weird way to calculate a shadow color, but does come out nicely
// because it appropriately handles antialiased input images.
ColorAdjust monochrome = new ColorAdjust();
monochrome.setBrightness(+0.5);
monochrome.setSaturation(-1.0);
gc.setEffect(monochrome);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(image, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.setEffect(null);
gc.drawImage(image, x, y);
}
private void drawWithShadowUsingStencil(GraphicsContext gc, Image image, double x, double y, int shadowLength, Color shadowColor) {
Image shadow = createShadowImage(image, shadowColor);
for (int offset = shadowLength; offset > 0; --offset) {
gc.drawImage(shadow, x + offset, y + offset / SHADOW_SLOPE_FACTOR);
}
gc.drawImage(image, x, y);
}
private Image createShadowImage(Image image, Color shadowColor) {
WritableImage shadow = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
PixelReader reader = shadow.getPixelReader();
PixelWriter writer = shadow.getPixelWriter();
for (int ix = 0; ix < image.getWidth(); ix++) {
for (int iy = 0; iy < image.getHeight(); iy++) {
int argb = reader.getArgb(ix, iy);
int a = (argb >> 24) & 0xFF;
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = argb & 0xFF;
// because we use a binary choice, we lose anti-alising info in the shadow so it looks a bit jaggy.
Color fill = (r > 0 || g > 0 || b > 0) ? shadowColor : Color.TRANSPARENT;
writer.setColor(ix, iy, fill);
}
}
return shadow;
}
private Image getImage() {
Label label = new Label("a");
label.setStyle("-fx-text-fill: forestgreen; -fx-background-color: transparent; -fx-font-size: " + FONT_SIZE + "px;");
Scene scene = new Scene(label, Color.TRANSPARENT);
SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
return label.snapshot(snapshotParameters, null);
}
public static void main(String[] args) {
launch();
}
}
内置的JavaFX有一个DropShadow效果,这几乎就是你想要的,尤其是当你将spread设置为1,radius设置为0时,然而,它只是生成一个单独的偏移阴影图像,而不是长阴影效果。
有一些替代文本和更短的 "long shadow":