Let's say that I want to load an shp file, do my stuff on it and save the map as an image.
In order to save an image I am using:
public void saveImage(final MapContent map, final String file, final int imageWidth) {
GTRenderer renderer = new StreamingRenderer();
renderer.setMapContent(map);
Rectangle imageBounds = null;
ReferencedEnvelope mapBounds = null;
try {
mapBounds = map.getMaxBounds();
double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0);
imageBounds = new Rectangle(0, 0, imageWidth, (int) Math.round(imageWidth * heightToWidth));
} catch (Exception e) {
// Failed to access map layers
throw new RuntimeException(e);
}
BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_RGB);
Graphics2D gr = image.createGraphics();
gr.setPaint(Color.WHITE);
gr.fill(imageBounds);
try {
renderer.paint(gr, imageBounds, mapBounds);
File fileToSave = new File(file);
ImageIO.write(image, "png", fileToSave);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
But, let's say I am doing something like this:
...
MapContent map = new MapContent();
map.setTitle("TEST");
map.addLayer(layer);
map.addLayer(shpLayer);
// zoom into the line
MapViewport viewport = new MapViewport(featureCollection.getBounds());
map.setViewport(viewport);
saveImage(map, "/tmp/img.png", 800);
1) The problem is that the zoom level isn't saved on the image file.Is there a way to save it?
2) When I am doing MapViewport(featureCollection.getBounds()); is there a way to extend a little bit the boundaries in order to have a better visual representation?
...
The reason that you aren't saving the map at the current zoom level is that in your saveImage method you have the line:
mapBounds = map.getMaxBounds();
which always uses the full extent of the map, you can change this to
mapBounds = map.getViewport().getBounds();
You can expand a bounding box by something like:
ReferencedEnvelope bounds = featureCollection.getBounds();
double delta = bounds.getWidth()/20.0; //5% on each side
bounds.expandBy(delta );
MapViewport viewport = new MapViewport(bounds);
map.setViewport(viewport );
A quicker (and easier) way to save a map from the GUI is to use a method like this which just saves exactly what is on the screen:
public void drawMapToImage(File outputFile, String outputType,
JMapPane mapPane) {
ImageOutputStream outputImageFile = null;
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(outputFile);
outputImageFile = ImageIO.createImageOutputStream(fileOutputStream);
RenderedImage bufferedImage = mapPane.getBaseImage();
ImageIO.write(bufferedImage, outputType, outputImageFile);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
try {
if (outputImageFile != null) {
outputImageFile.flush();
outputImageFile.close();
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (IOException e) {// don't care now
}
}
}
Related
I created a plot on Canvas. Now I want to add the image to an Excel file.
I know how to get a WritableImage from Canvas and I know that I need an InputStream to write an image in Excel with addPicture(). The problem is how to link these two.
I could save this image to file and then open and load it to Excel, but maybe there is a way to avoid this?
You could use a PipedInputStream/PipedOutputStream to accomplish this:
Image image = ...
BufferedImage bImage = SwingFXUtils.fromFXImage(image, null);
PipedOutputStream pos;
try (PipedInputStream pis = new PipedInputStream()) {
pos = new PipedOutputStream(pis);
new Thread(() -> {
try {
ImageIO.write(bImage, "png", pos);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}).start();
workbook.addPicture(pis, Workbook.PICTURE_TYPE_PNG);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
or you could write the data to a array using ByteArrayOutputStream:
Image image = ...
BufferedImage bImage = SwingFXUtils.fromFXImage(image, null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(bImage, "png", bos);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
workbook.addPicture(bos.toByteArray(), Workbook.PICTURE_TYPE_PNG);
For startes, i know libgdx is primarily used for gaming. I have just found out about it and wanted to try to give it an extra other (possible) purpose, for example a simple photo frame. The below code is just a part of a proof of concept and when it runs as it should evolve to a bigger app.
Below i have posted a very simple class at what i'm up to now. What it does is every [num] seconds, in a different thread, it loads an image from disk, put it in a pixmap and creates a texture from it on the GL thread (if i understand everything correctly).
I came to this code after a lot of trial and error, It took me an hour to found out that a texture should be created in the OpenGL thread. When the texture is created outside the thread the images where just big black boxes without the loaded texture.
Well, when i ran this class version with textures created on the thread I finally see the image showing, and nicely fading every [num] seconds.
But, after 15 executions the images are starting to appear as black boxes again as if the texture is created outside the GL thread. I'm not getting any exceptions printed in the console.
The application is running on a Raspberry Pi with memory split 128/128. The images are jpeg images in 1920*1080 (not progressive). Memory usage is as follows according to top:
VIRT: 249m
RES: 37m
SHR: 10m
Command line is: java -Xmx128M -DPI=true -DLWJGJ_BACKEND=GLES -Djava.library.path=libs:/opt/vc/lib:. -classpath *:. org.pidome.raspberry.mirrorclient.BootStrapper
I see the RES rising when a new image is loaded but after loading it is back to 37.
The System.out.println("Swap counter: " +swapCounter); keeps giving me output when the thread is run.
Could one of you guys point me in the right direction into solving the issue that after 15 iterations the textures are not shown anymore and the images are solid black?
Here is my current code (name PhotosActor is misleading, a result of first trying it to be an Actor):
public class PhotosActor {
List<Image> images = new ArrayList<>();
private String imgDir = "appimages/photos/";
List<String> fileSet = new ArrayList<>();
private final ScheduledExecutorService changeExecutor = Executors.newSingleThreadScheduledExecutor();
Stage stage;
int swapCounter = 0;
public PhotosActor(Stage stage) {
this.stage = stage;
}
public final void preload(){
loadFileSet();
changeExecutor.scheduleAtFixedRate(switchimg(), 10, 10, TimeUnit.SECONDS);
}
private Runnable switchimg(){
Runnable run = () -> {
try {
swapCounter++;
FileInputStream input = new FileInputStream(fileSet.get(new Random().nextInt(fileSet.size())));
Gdx2DPixmap gpm = new Gdx2DPixmap(input, Gdx2DPixmap.GDX2D_FORMAT_RGB888);
input.close();
Pixmap map = new Pixmap(gpm);
Gdx.app.postRunnable(() -> {
System.out.println("Swap counter: " +swapCounter);
Texture tex = new Texture(map);
map.dispose();
Image newImg = new Image(tex);
newImg.addAction(Actions.sequence(Actions.alpha(0),Actions.fadeIn(1f),Actions.delay(5),Actions.run(() -> {
if(images.size()>1){
Image oldImg = images.remove(1);
oldImg.getActions().clear();
oldImg.remove();
}
})));
images.add(0,newImg);
stage.addActor(newImg);
newImg.toBack();
if(images.size()>1){ images.get(1).toBack(); }
});
} catch (Exception ex) {
Logger.getLogger(PhotosActor.class.getName()).log(Level.SEVERE, null, ex);
}
};
return run;
}
private void loadFileSet(){
File[] files = new File(imgDir).listFiles();
for (File file : files) {
if (file.isFile()) {
System.out.println("Loading: " + imgDir + file.getName());
fileSet.add(imgDir + file.getName());
}
}
}
}
Thanks in advance and cheers,
John.
I was able to resolve this myself, A couple of minutes ago it struck me that i have to dispose the texture. I was in the believe that removing the image also removed the texture. Which it clearly did not (or i have to update to more recent version).
So what i did was create a new class extending the image class:
public class PhotoImage extends Image {
Texture tex;
public PhotoImage(Texture tex){
super(tex);
this.tex = tex;
}
public void dispose(){
try {
this.tex.dispose();
} catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
On all the location i was refering to the image class i changed it to this PhotoImage class. The class modified some now looks like:
public class PhotosActor {
List<PhotoImage> images = new ArrayList<>();
private String imgDir = "appimages/photos/";
List<String> fileSet = new ArrayList<>();
private final ScheduledExecutorService changeExecutor = Executors.newSingleThreadScheduledExecutor();
Stage stage;
int swapCounter = 0;
public PhotosActor(Stage stage) {
this.stage = stage;
}
public final void preload(){
loadFileSet();
changeExecutor.scheduleAtFixedRate(switchimg(), 10, 10, TimeUnit.SECONDS);
}
private Runnable switchimg(){
Runnable run = () -> {
try {
swapCounter++;
byte[] byteResult = readLocalRandomFile();
Pixmap map = new Pixmap(byteResult, 0, byteResult.length);
Gdx.app.postRunnable(() -> {
System.out.println("Swap counter: " +swapCounter);
Texture tex = new Texture(map);
map.dispose();
PhotoImage newImg = new PhotoImage(tex);
images.add(0,newImg);
stage.addActor(newImg);
addTransform(newImg);
});
} catch (Exception ex) {
Logger.getLogger(PhotosActor.class.getName()).log(Level.SEVERE, null, ex);
}
};
return run;
}
public void addTransform(Image img){
switch(new Random().nextInt(3)){
case 0:
img.toBack();
if(images.size()>1){ images.get(1).toBack(); }
img.addAction(Actions.sequence(Actions.alpha(0),Actions.fadeIn(1f),Actions.delay(5),Actions.run(() -> {
removeOldImg();
})));
break;
case 1:
img.toBack();
if(images.size()>1){ images.get(1).toBack(); }
img.setPosition(1920f, 1080f);
img.addAction(Actions.sequence(Actions.moveTo(0f, 0f, 5f),Actions.run(() -> {
removeOldImg();
})));
break;
case 2:
img.toBack();
if(images.size()>1){ images.get(1).toBack(); }
img.setScale(0f, 0f);
img.setPosition(960f, 540f);
img.addAction(Actions.sequence(Actions.parallel(Actions.scaleTo(1f, 1f, 5f), Actions.moveTo(0f, 0f, 5f)),Actions.run(() -> {
removeOldImg();
})));
break;
}
}
private void removeOldImg(){
if(images.size()>1){
PhotoImage oldImg = images.remove(1);
oldImg.remove();
oldImg.getActions().clear();
oldImg.dispose();
}
System.out.println("Amount of images: " + images.size());
}
private byte[] readLocalRandomFile() throws Exception{
FileInputStream input = null;
try {
input = new FileInputStream(fileSet.get(new Random().nextInt(fileSet.size())));
ByteArrayOutputStream out;
try (InputStream in = new BufferedInputStream(input)) {
out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n = 0;
while (-1 != (n = in.read(buf))) {
out.write(buf, 0, n);
}
out.close();
return out.toByteArray();
} catch (IOException ex) {
Logger.getLogger(PhotosActor.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (FileNotFoundException ex) {
Logger.getLogger(PhotosActor.class.getName()).log(Level.SEVERE, null, ex);
}
throw new Exception("No data");
}
private void loadFileSet(){
File[] files = new File(imgDir).listFiles();
for (File file : files) {
if (file.isFile()) {
System.out.println("Loading: " + imgDir + file.getName());
fileSet.add(imgDir + file.getName());
}
}
}
}
In the remove function i now have added
oldImg.dispose();
to get rid of the texture. Image transitions are now happy running on 50+ fps on the Raspberry Pi and the image rotation counter is on: 88 now. If there where people thinking thanks for your time!
I am using SVGSalamander.
My code loads a svg image and sets it as background of a JDesktopPane.
File f = new File("awesome_tiger.svg");
SVGUniverse svgUniverse = new SVGUniverse();
try {
SVGDiagram diagram = svgUniverse.getDiagram(svgUniverse.loadSVG(f.toURL()));
try {
diagram.render(g);
}
catch(Exception ex) {System.out.println(ex);}}
catch (Exception ex2) {System.out.println(ex2);}
How can I achieve, that the image fills the window/frame completely and resizes with it?
Seems SVGSalamander has the method isScaleToFit() but how can I use it?
I use for antiAlias this: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); (this is how it is written in the SVGIcon and SVGPanel classes)
Edit: solved it
AffineTransform at = new AffineTransform();
at.setToScale(jdpPane.getWidth()/diagram.getWidth(), jdpPane.getWidth()/diagram.getWidth());
g.transform(at);
diagram.render(g);
scales it proportionally
We can use an AffineTransform and the setToScale method
File f = new File("awesome_tiger.svg");
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
SVGUniverse svgUniverse = new SVGUniverse();
try {
SVGDiagram diagram = svgUniverse.getDiagram(svgUniverse.loadSVG(f.toURI().toURL()));
try {
AffineTransform at = new AffineTransform();
at.setToScale(jdpPane.getWidth()/diagram.getWidth(), jdpPane.getWidth()/diagram.getWidth());
g.transform(at);
diagram.render(g);
}
catch(Exception e2) {System.out.println(e2);}}
catch (Exception ex) {System.out.println(ex);}
this is the code with which I am overriding the paint() method
Sometimes it works, sometimes it doesn't. I am sharing this picture on Facebook, and sometimes it is ok:
And sometimes it is partial black (only the Google map!):
I have a separate thread doing this screenshot:
new Thread() {
#Override
public void run() {
// Get root view
View view = mapView.getRootView();
view.setDrawingCacheEnabled(true);
// Create the bitmap to use to draw the screenshot
final Bitmap bitmap = Bitmap.createBitmap(
getWindowManager().getDefaultDisplay().getWidth(), getWindowManager().getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_4444);
final Canvas canvas = new Canvas(bitmap);
// Get current theme to know which background to use
final Theme theme = activity.getTheme();
final TypedArray ta = theme
.obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
final int res = ta.getResourceId(0, 0);
final Drawable background = activity.getResources().getDrawable(res);
// Draw background
background.draw(canvas);
// Draw views
view.draw(canvas);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 90, baos); //bm is the bitmap object
byte[] b = baos.toByteArray();
Bundle params = new Bundle();
params.putByteArray("source", b);
params.putString("message", "genie in a bottle");
try {
//String resp =
facebook.request("me/photos", params, "POST");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}.start();
You can create the Bitmap object directly from the screen using:
bitmap = Bitmap.createBitmap(view.getDrawingCache());
Regards.
I got a BufferedImage and want to rescale it before saving it as an jpg/png.
I got the following code:
private BufferedImage rescaleTo(BufferedImage img,int minWidth,int minHeight) {
BufferedImage buf = toBufferedImage(img.getScaledInstance(minWidth, minHeight, Image.SCALE_DEFAULT));
BufferedImage ret = new BufferedImage(buf.getWidth(null),buf.getHeight(null),BufferedImage.TYPE_INT_ARGB);
return ret;
}
public BufferedImage toBufferedImage(Image img) {
BufferedImage ret = new BufferedImage(img.getWidth(null),img.getHeight(null),BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = ret.createGraphics();
g2.drawImage(img,0,0,null);
return ret;
}
public String saveTo(BufferedImage image,String URI) throws UtilityException {
try {
if(image == null)
System.out.println("dododod");
ImageIO.write(image, _type, new File(URI));
} catch (IOException e) {
throw new UtilityException(e.getLocalizedMessage());
}
return URI;
}
But as an result I just get a black picture. It must have to do with the rescaling as when I skip it I can save the expected picture.
As a test, set _type="png" and also use file extension .png when you make the call to ImageIO.write(image, _type, new File(URI));. I had issues like you describe and I started writing type PNG and all works fine. Unfortunately, I never went back to debug why I could not write type JPG, GIF etc.