Is it possible to skew or distort an Image object in Java? I 'pull' one side of an image out, making it seem closer to me. (LIke 3D).
Any suggestions?
Yes. Lots of ways but I would start with the Advanced Imaging API. It provides a ton of advanced imaging functionality.
But just to do the type of transform that you're talking about you might just need an Affine Transform. Sample results here for the previous link.
You can also do this with JavaFX.
The following example uses PerspectiveTransform and a bit of rotation on the BufferedImage.
It turns this image
into this
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
/**
* Distorts images using transformations.
* <p>
* Created by Matthias Braun on 2018-09-05.
*/
public class Distortion {
public static void main(String... args) throws IOException {
URL imgUrl = new URL("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a");
BufferedImage img = ImageIO.read(imgUrl);
BufferedImage distorted = distortImg(img);
File newImgFile = new File(System.getenv("HOME") + "/distorted.png");
System.out.println("Saving to: " + newImgFile);
ImageIO.write(distorted, "png", newImgFile);
// Since we started a JavaFX thread in distortImg we have to shut it down. Otherwise the JVM won't exit
Platform.exit();
}
/**
* Applies perspective transformations to a copy of this {#code image} and rotates it.
* <p>
* Since this method starts a JavaFX thread, it's important to call {#link Platform#exit()} at the end of
* your application. Otherwise the thread will prevent the JVM from shutting down.
*
* #param image the image we want to distort
* #return the distorted image
*/
private static BufferedImage distortImg(BufferedImage image) {
// Necessary to initialize the JavaFX platform and to avoid "IllegalStateException: Toolkit not initialized"
new JFXPanel();
// This array allows us to get the distorted image out of the runLater closure below
final BufferedImage[] imageContainer = new BufferedImage[1];
// We use this latch to await the end of the JavaFX thread. Otherwise this method would finish before
// the thread creates the distorted image
final CountDownLatch latch = new CountDownLatch(1);
// To avoid "IllegalStateException: Not on FX application thread" we start a JavaFX thread
Platform.runLater(() -> {
int width = image.getWidth();
int height = image.getHeight();
Canvas canvas = new Canvas(width, height);
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
ImageView imageView = new ImageView(SwingFXUtils.toFXImage(image, null));
PerspectiveTransform trans = new PerspectiveTransform();
trans.setUlx(0);
trans.setUly(height / 4);
trans.setUrx(width);
trans.setUry(0);
trans.setLrx(width);
trans.setLry(height);
trans.setLlx(0);
trans.setLly(height - height / 2);
imageView.setEffect(trans);
imageView.setRotate(2);
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
Image newImage = imageView.snapshot(params, null);
graphicsContext.drawImage(newImage, 0, 0);
imageContainer[0] = SwingFXUtils.fromFXImage(newImage, image);
// Work is done, we decrement the latch which we used for awaiting the end of this thread
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return imageContainer[0];
}
}
Related
I'm in the same situation as here : https://sourceforge.net/p/geotools/mailman/message/35977998/
I am working on a Maps Application using geotools (WMS + WFS for grids) and resizing my JavaFX Canvas works well when I am reducing the size of the canvas, but a part of the image is not rendered when I expend my window (the canvas is expended too).
Is there a solution ?
I'm posting the same example as the one in the link above :
import java.awt.Color;
import java.awt.Rectangle;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.jfree.fx.FXGraphics2D;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ResizingTest extends Application {
#Override
public void start(Stage stage) {
Canvas canvas = new Canvas(640, 480);
BorderPane root = new BorderPane(canvas);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
// Create bindings for resizing.
canvas.widthProperty().bind(root.widthProperty());
canvas.heightProperty().bind(root.heightProperty());
SimpleFeatureTypeBuilder lineFeatureTypeBuilder = new
SimpleFeatureTypeBuilder();
lineFeatureTypeBuilder.setName("LineFeatureType");
lineFeatureTypeBuilder.setCRS(DefaultGeographicCRS.WGS84);
lineFeatureTypeBuilder.add("the_geom", LineString.class,
DefaultGeographicCRS.WGS84);
SimpleFeatureType lineFeatureType = lineFeatureTypeBuilder.buildFe
atureType();
SimpleFeatureBuilder lineFeatureBuilder = new
SimpleFeatureBuilder(lineFeatureType);
DefaultFeatureCollection lines = new DefaultFeatureCollection();
Coordinate[][] cs = {
{ new Coordinate(-1, 42), new Coordinate(4, 46) },
{ new Coordinate(-1, 46), new Coordinate(4, 42) }
};
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFa
ctory();
for(Coordinate [] c : cs) {
LineString line = geometryFactory.createLineString(c);
lineFeatureBuilder.add(line);
SimpleFeature feature = lineFeatureBuilder.buildFeature(null);
lines.add(feature);
}
MapContent map = new MapContent();
Style style = SLD.createLineStyle(Color.RED, 1);
Layer layer = new FeatureLayer(lines, style);
map.addLayer(layer);
//map.getViewport().setBounds(new ReferencedEnvelope(-1, 4, 42, 46,
DefaultGeographicCRS.WGS84));
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext g = canvas.getGraphicsContext2D();
FXGraphics2D graphics = new FXGraphics2D(g);
graphics.setBackground(java.awt.Color.BLUE);
Rectangle rectangle = new Rectangle( (int)
canvas.getWidth(), (int) canvas.getHeight());
graphics.clearRect(0, 0, (int) rectangle.getWidth(), (int)
rectangle.getHeight());
graphics.drawRect(100, 100, 100, 100);
map.getViewport().setScreenArea(rectangle); // Necessary ?
StreamingRenderer renderer = new StreamingRenderer();
renderer.setMapContent(map);
renderer.paint(graphics, rectangle,
map.getViewport().getBounds());
System.out.println("ScreenArea: " +
map.getViewport().getScreenArea() + " - Viewport: " +
map.getViewport().getBounds());
}
};
loop.start();
}
public static void main(String[] args) {
launch(args);
}
}
When we expand the window, a part of the canvas is not rendered anymore, resulting on a "correct" cross (because it still goes from upper left to lower right corner), but cropped ! Anything drawn on the cropped part is not rendered
Edit :
I am not trying to make the canvas resizable, it already is (Proof : The cross goes from upper left to lower right pixel of canvas). The real issue is the rendering of the map that is cropped (we can't see the full cross).
Resizing a canvas can be tricky in JavaFX. Here is a SO answer which may be helpful for you.
Automatically resize Canvas to fill the enclosing Parent
I finally found a fix !
As mentionned in geotools' StreamingRenderer code :
"the way this thing is built is a mess if you try to use it in a
multithreaded environment"
Avoid using StreamingRenderer in an AnimationLoop, Platform.runLater(), etc...
I called the draw function everytime the map was updated instead, and it works as expected ! :)
I have been trying to make a simple application that opens and closes the camera. So far, the opening part works, but every time I try to close the camera, this series of events happen:
The capture stops capturing, but the lights still on and the image does not get cleared.
The second time i press the stop button, the image gets cleared.
After that, whenever i press the start button, regardless on whether or not I did step 2, it does nothing and gives me this error:
[ WARN:1] videoio(MSMF): OnReadSample() is called with error status:
-1072875772 [ WARN:1] videoio(MSMF): async ReadSample() call is failed with error status: -1072875772 [ WARN:2] videoio(MSMF): can't grab
frame. Error: -1072875772
package application;
import java.io.ByteArrayInputStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
public class FXHelloCVController {
#FXML
private AnchorPane MainPane;
#FXML
private Button StartButton;
#FXML
private ImageView currentFrame;
#FXML
private Button StopButton;
private ScheduledExecutorService timer;
private VideoCapture capture = new VideoCapture();
private boolean cameraActive = false;
private static int cameraID = 0;
#FXML
void startCamera(ActionEvent event) {
if(!this.cameraActive) {
//if the camera is not active, open the camera
cameraActive = true;
this.capture.open(cameraID);
if(this.capture.isOpened()) {
//if the stream is available, run at 30fps (33 ms)
Runnable frameGrabber = new Runnable(){
#Override
public void run() {
// TODO Auto-generated method stub
Image imageToShow = grabFrame();
Platform.runLater(new Runnable() {
#Override public void run() {
currentFrame.setImage(imageToShow); }
});
}
};
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33,
TimeUnit.MILLISECONDS);
}
}
}
private Image grabFrame() {
// TODO Auto-generated method stub
Mat frame = new Mat();
MatOfByte buffer = new MatOfByte();
// check if the capture is open
if (this.capture.isOpened()){
try{
// read the current frame
this.capture.read(frame);
// if the frame is not empty, process it
if (!frame.empty()){
Imgcodecs.imencode(".png", frame, buffer);
}
}catch(Exception e){
// log the error
System.err.println("Exception during the image elaboration: " +
e);
}
}
return new Image(new ByteArrayInputStream(buffer.toArray()));
}
#FXML
private void stopCamera(){
cameraActive = false;
if (this.timer!=null && !this.timer.isShutdown()){
try{
// stop the timer
this.timer.awaitTermination(33, TimeUnit.MILLISECONDS);
this.timer.shutdown();
this.currentFrame.setImage(null);
}catch (InterruptedException e){
e.printStackTrace();
}
}
This right here is my controller object, can anyone help me find out whats wrong?
I got this exact same error because of a hardware failure. I am on Windows, and it stopped working after an update. Could be a driver issue also. I strongly suspect it originated from the platform level.
I changed my webcam, and it was all well again.
I think your stopCamera method needs capture.release().
Probably unrelated but I think the timer stuff is the wrong way to go. Even if the camera is set to 30 FPS, skew between its streaming clock and your Java timing will eventually cause a breakdown. Use an infinite loop in a thread instead and know that capture.read() will suspend until a frame is available. The infinite loop will work correctly at any FPS, provided you have enough processing power to keep up.
In my case I couldn't even start the camera. It was because 'Kaspersky Endpoint Security', after exiting the virus guard it started working.
Is it somehow possible to render a GUI to a BufferedImage or another kind of memory image without displaying it on a screen ?
I know this will loose all kinds of hardware acceleration, but for a simple GUI that is refreshed only once or twice a second this should not be an issue.
Already tried to get JavaFX to output an image, but i can't find a way to leave out rendering on a screen first. Does anyone know a way to do this with JavaFX or Swing ?
It is no problem to draw a simple GUI myself using simple image manipulations, but then i would have to do it all by hand and using Swing or FX would make it much easier.
Edit:
To make it a bit more clear, i don't have an active display, but i can save an image which then gets displayed through other means. To be exact its a raspberry pi, but without a primary display device with a connected tft display using the GPIO port. So i can't render the UI directly to a display device, but need to create an image that i can save at a specific location. All methods i have tried so far need a primary display device.
Yes, it is possible to render a GUI to an image offscreen.
Here is a sample using JavaFX, with example image output as below:
The example works by rendering the chart to an scene which is not added to any window and no window (Stage in JavaFX terminology) is ever shown. The snapshot method is used to take a snapshot of the node and then ImageIO utilities are used to save the snapshot to disk.
Rendering of the offscreen scene will be hardware accelerated if the underlying hardware/software platform supports it.
import javafx.application.*;
import javafx.collections.*;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.*;
import javafx.scene.chart.PieChart;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.*;
public class OffscreenImageRecorder extends Application {
private static final Logger logger = Logger.getLogger(OffscreenImageRecorder.class.getName());
private static final String IMAGE_TYPE = "png";
private static final String IMAGE_FILENAME = "image." + IMAGE_TYPE;
private static final String WORKING_DIR = System.getProperty("user.dir");
private static final String IMAGE_PATH = new File(WORKING_DIR, IMAGE_FILENAME).getPath();
private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private final Random random = new Random();
private final int CHART_SIZE = 400;
#Override
public void start(Stage stage) throws IOException {
Parent chart = createChart();
Image image = snapshot(chart);
exportPng(SwingFXUtils.fromFXImage(image, null), IMAGE_PATH);
Platform.exit();
}
private Parent createChart() {
// create a chart.
final PieChart chart = new PieChart();
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Grapefruit", random.nextInt(30)),
new PieChart.Data("Oranges", random.nextInt(30)),
new PieChart.Data("Plums", random.nextInt(30)),
new PieChart.Data("Pears", random.nextInt(30)),
new PieChart.Data("Apples", random.nextInt(30))
);
chart.setData(pieChartData);
chart.setTitle("Imported Fruits - " + dateFormat.format(new Date()));
// It is important for snapshots that the chart is not animated
// otherwise we could get a snapshot of the chart before the
// data display has been animated in.
chart.setAnimated(false);
chart.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
chart.setPrefSize(CHART_SIZE, CHART_SIZE);
chart.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
return chart;
}
private Image snapshot(final Parent sourceNode) {
// Note: if the source node is not in a scene, css styles will not
// be applied during a snapshot which may result in incorrect rendering.
final Scene snapshotScene = new Scene(sourceNode);
return sourceNode.snapshot(
new SnapshotParameters(),
null
);
}
private void exportPng(BufferedImage image, String filename) {
try {
ImageIO.write(image, IMAGE_TYPE, new File(filename));
logger.log(Level.INFO, "Wrote image to: " + filename);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
}
It's a bit of a hack, but you could create a frame and position it on a invisible location (using Swing in this example):
frame = new JFrame("Invisible frame");
frame.setBounds(-1000, 100, 640, 480);
here's the problem: I have several images and would like to use them when displaying a HTML in JavaFX's WebView.
Current implementation is very obvious: there is a file, which is linked to in the HTML content. I assume that WebView does not regress from JEditorPane and will only do a single I/O operation even if the image is referenced 10 000 times throughout the content.
However, it would be great to have a single Image instance and feed it to WebView when it encounters the relevant <img> tag.
I have seen that there is a great half-solution involving URL handling, but the problem remains: you have an Image instance that you convert to a storage format (BMP, PNG with proprietary extensions, etc) and keep that in memory. However, this means that each time WebView desires an image resolution, it must then manually parse the image from binary data. In the end, you just have a file mapped to memory plus an internal Image instance instead of a shared Image instance.
With JEditorPane, you could push Images to its image cache and get rid of such problems. Unfortunately, since Java 7, that component is unusable and is out of question.
Basically, is there any chance WebView/WebEngine maintains such a cache/equivalent and is there a way to pre-populate it?
/**
* Encodes the image as a whole into PNG, then into Base64 and finally into an URI suitable for the HTML {#code <img>} tag.
*
* #param image an image
* #return image as URI (image within the URI)
* #throws IIOException if there is a fault with an image writer
* #throws IOException in case of a general I/O error
*/
public static final String getImageSrcForWebEngine(RenderedImage image) throws IIOException, IOException
{
final ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", output);
return "data:base64," + Base64.getMimeEncoder().encodeToString(output.toByteArray());
}
Example of usage:
RenderedImage image = […];
String tag = "<img src=\"" + getImageSrcForWebEngine(image) + "\" border=\"0\" />";
SSCCE:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewWithMemoryImages extends Application
{
private static String IMAGE_IN_MEMORY;
#Override
public void start(Stage primaryStage)
{
WebView webView = new WebView();
webView.getEngine().loadContent("<html><body><img src=\"" + IMAGE_IN_MEMORY + "\"></body></html>");
primaryStage.setScene(new Scene(webView, 420, 420));
primaryStage.show();
}
public static void main(String[] args) throws Exception
{
BufferedImage image = new BufferedImage(400, 400, BufferedImage.TYPE_INT_BGR);
Graphics2D g = image.createGraphics();
try
{
g.setColor(Color.RED);
g.fillRect(0, 0, 400, 400);
g.setColor(Color.WHITE);
g.fillRect(50, 50, 300, 300);
g.setColor(Color.BLACK);
g.fillRect(100, 100, 200, 200);
g.drawString("No image files were used in this WebView.", 90, 70);
}
finally
{
g.dispose();
}
IMAGE_IN_MEMORY = getImageSrcForWebEngine(image);
launch(args);
}
public static String getImageSrcForWebEngine(RenderedImage image) throws IIOException, IOException
{
final ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", output);
return "data:base64," + Base64.getMimeEncoder().encodeToString(output.toByteArray());
}
}
I need to get the background image from a PowerPoint slide using java. I am aware of the Apache POI project. I can find material for getting text and shapes from the slides, but not the actual background. Does anyone have any suggestions?
EDIT: I have creaked the following code using the suggested link. This code seems to grab the contents of the slide, but not exactly the background. The resulting images are white for the background.
I tried it with this PowerPoint
package PowerPointProcessing;
import Logging.LogRunner;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.model.Background;
import org.apache.poi.hslf.model.Fill;
import org.apache.poi.hslf.model.Shape;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.usermodel.SlideShow;
/**
*
* #author dvargo
*/
public class PPI
{
Dimension pageSize;
public Slide[] theSlides;
public PPI(String powerPointFilePath)
{
SlideShow ppt = null;
//open the presentation
try
{
ppt = new SlideShow(new HSLFSlideShow(powerPointFilePath));
}
catch(Exception e)
{
LogRunner.getLogger().severe("Could not open the powerpoint presentation");
return;
}
//get all the slides
theSlides = ppt.getSlides();
//see how many slides there are
int numberOfSlides = theSlides.length;
pageSize = ppt.getPageSize();
}
public BufferedImage getBackground(Slide theSlide)
{
Background background;
background = theSlide.getBackground();
Fill f = background.getFill();
Color color = f.getForegroundColor();
Shape[] theShapes = theSlide.getShapes();
BufferedImage img = new BufferedImage(pageSize.width, pageSize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setPaint(color);
graphics.fill(new Rectangle2D.Float(0, 0, pageSize.width, pageSize.height));
theSlide.draw(graphics);
return img;
}
public static void main(String[] args) {
PPI ppi = new PPI("C:\\Documents and Settings\\dvargo\\Desktop\\Cludder\\a.ppt");
int count= 0;
for (Slide currSlide : ppi.theSlides)
{
BufferedImage img = ppi.getBackground(currSlide);
try
{
ImageIO.write(img, "jpeg", new File("C:\\Documents and Settings\\dvargo\\Desktop\\ppt\\" + count + ".jpeg"));
}
catch (IOException ex)
{
Logger.getLogger(PPI.class.getName()).log(Level.SEVERE, null, ex);
}
count++;
}
}
}
Looking at the code from this question:
Extracting images from pptx with apache poi
Looks like it should be something like:
Background background = slides[current].getBackground();
Fill f = background.getFill();
Color color = f.getForegroundColor();