How to save a high DPI snapshot of a JavaFX Canvas - javafx

I have created an image on a Canvas which is scaled down for display using a transformation. It is also in a ScrollPane which means only a part of the image is visible.
I need to take a snapshot of the entire canvas and save this as a high-resolution image. When I use Canvas.snapshot I get a Writable image of the visible part of the image after scaling down. This results in a low-res partial image being saved.
So how do I go about creating a snapshot which includes the entire canvas (not only the viewport of the scrollpane) and with the resolution before the transformation downwards?
I am not doing anything fancy currently, just this:
public WritableImage getPackageCanvasSnapshot()
{
SnapshotParameters param = new SnapshotParameters();
param.setDepthBuffer(true);
return packageCanvas.snapshot(param, null);
}

I did the following to get a canvas snapshot on a Retina display with a pixelScaleFactor of 2.0. It worked for me.
public static WritableImage pixelScaleAwareCanvasSnapshot(Canvas canvas, double pixelScale) {
WritableImage writableImage = new WritableImage((int)Math.rint(pixelScale*canvas.getWidth()), (int)Math.rint(pixelScale*canvas.getHeight()));
SnapshotParameters spa = new SnapshotParameters();
spa.setTransform(Transform.scale(pixelScale, pixelScale));
return canvas.snapshot(spa, writableImage);
}

Related

Display JavaFX Image without blur/dithering when zooming in

I'm trying to create a image editor tool, where I have X- and Y-axis. Now I want to support the user by seeing each pixel when zooming in so that they can work more exactly. In comparison you should take a look at paint.net where you can see the pixels when zooming in.
I'll give you a code snippet and perhaps you might have a hint for me where I should dig into (API or code examples). I've found nothing like it in the JAVAFX API documentation. I want to use an ImageView instance but not a Canvas.
ScrollPane scrollPane = new ScrollPane ();
BorderPane borderPane = new BorderPane();
Group group = new Group();
DoubleProperty zoomProperty = new SimpleDoubleProperty(1);
Scale scaleTransformation = new Scale();
scaleTransformation.xProperty().bind(zoomProperty);
scaleTransformation.yProperty().bind(zoomProperty);
ImageView viewer = new ImageView(getBackgroundImage());
viewer.setFitWidth(600);
viewer.setPreserveRatio(true);
viewer.setDisable(true);
viewer.setSmooth(false);
group.getChildren().add(viewer);
this.addEventFilter(ScrollEvent.SCROLL, new EventHandler<ScrollEvent>(){
#Override
public void handle(ScrollEvent se) {
if (se.isControlDown() && se.getDeltaY() > 0) {
double value = zoomProperty.get() * 1.1;
zoomProperty.set(value);
se.consume();
} else if (se.isControlDown() && se.getDeltaY() < 0) {
double value = zoomProperty.get() / 1.1;
zoomProperty.set(value);
se.consume();
}
}
});
borderPane.setCenter(group);
scrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scrollPane setContent(new Group(borderPane));
Thanks for you help.
EDIT
The related question and it's answers do not solve my problem (JavaFX ImageView without any smoothing). The solution 1-4 do not work. Only the hack does. I guess I could be related the node structure I use. I've adjusted my code.
I have found one solution (referring to the possible duplicate) and using a variant of method 1.
In my image constructor I set the requested size to something large - so for an image that I know is about 1000 pixels wide I request an image 10000 pixels wide:
Image image = new Image(file.toURI().toString(), 10000.0, 10000.0, true, false);
I am not sure if this is giving a true view of the pixels, or if it is smoothing at a very low level...... but it works for my needs.

ImageView not resizing image

I want to use an ImageView to resize an image, however the image is not being resized:
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
imageView.setFitHeight(40);
System.out.println("imageview image width = " + imageView.getImage().getWidth());
System.out.println("imageview image height = " + imageView.getImage().getHeight());
The output is
imageview image width = 674.0
imageview image height = 888.0
However, the width should be 40. My ImageView is not attached to any scene and I also don't want to attach it, it shall only be used for image resizing. Is there any way to force the ImageView to resize its image, even though the ImageView is not attached to any scene? The reason I am using an ImageView for resizing is, that I want to resize an Image in RAM, without reading it again from the disk, please see this question for more details.
Thanks for any hint!
Using an ImageView for resizing seems to be very hacky.
A better approach is to convert your Image into a BufferedImage and do the resizing the old way. (JavaFx does not (yet) provide an internal way to resize memory images)
int width = 500; // desired size
int height = 400;
Image original = ...; // fx image
BufferedImage img = new BufferedImage(
(int)original.getWidth(),
(int)original.getHeight(),
BufferedImage.TYPE_INT_ARGB);
SwingFXUtils.fromFXImage(original, img);
BufferedImage rescaled = Scalr.rescaleImage(img, width, heigth); // the actual rescale
// convert back to FX image
WritableImage rescaledFX = new WritableImage(width, heigth);
SwingFXUtils.toFXImage(rescaled, rescaledFX);
Where as Scalr is a nice library for resizing images in native java. Obviously, you can use other/simpler methods of rescaling, but the image quality won't be that nice.

How can an image be cropped based on a set scale of random image?

Working on allowing the upload of images which can range in a variety of size, then allowing to crop a predefined area of the image for a thumbnail.
The thumbnail size is predefined to 150x150. Using the Jcrop.js tool to select a section of the image.
Problem:
When displaying the uploaded image in a smaller size than the original image by implementing set height/width on the image rendered, then there is a scale factor that comes into play when selecting an area to crop.
You either have to scale down the cropping area proportionately or you have to scale the image in relation to the actual image's size in comparison to its displayed size.
Question:
How do I figure out the scale of the browser displayed image vs. original image? I am currently using the following code to save the image, but how would I take into consideration the scaling?
public static Image CropImage(Image originalImage, int x, int y, int width, int height)
{
var bmp = new Bitmap(width, height);
bmp.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
using (var graphic = Graphics.FromImage(bmp))
{
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode.HighSpeed;
graphic.DrawImage(originalImage, new Rectangle(0, 0, width, height), x, y, width, height, GraphicsUnit.Pixel);
return bmp;
}
}
Bonus Question:
Another problem I discovered, is that there seems to be no efficient way to transfer the original file's ImageFormat when creating a new Bitmap which creates a ImageFormatMemoryBMP and when you attempt to call Bitmap.Save(memorystream, original rawformat) it will blow up. And bitmap RawFormat has no setter.
So how can you set the format on a new bitmap?
I think perhaps that this problem is solved purely on the front end, no need to use any server side for this.
Jcrop has a built in scale factor handler.
http://deepliquid.com/content/Jcrop_Sizing_Issues.html
Now you can use this in two ways, as I understand it. Either to 'resize' the image for you on the front end using 'box sizing', or you can tell it the 'truesize' of the image and it will work out the scale factor and handle the coordinates for you on it's own.
Box sizing
$('#cropbox').Jcrop({ boxWidth: 450, boxHeight: 400 });
True Size
$.Jcrop('#cropbox',{ trueSize: [500,370] });
Using the true size method you will need to invoke jcrop using the api method:
http://deepliquid.com/content/Jcrop_API.html#API_Invocation_Method
var jcrop_api,
options = { trueSize: [500,370] };
$('#target').Jcrop(options,function(){
jcrop_api = this;
});
Good luck!

Flex saving canvas and dropped image

I have an image loaded from an url and added to canvas as child. Then I am drag and dropping another image on it which also uses the senocular transform so the image can be transformed on the canvas. I have coded in such way that the transform handles shows up only after it's dropped on canvas. The image shows up correctly. But I am trying to save the result image (that is the main image and the dropped image on top of it), I only end up with the main image that was loaded earlier. The dropped image doesn't show up.
Below is the code for handleDrop() that is fired on dragDrop event and prepares the final image. What am I doing wrong?
var dragInitiator:IUIComponent = dragEvent.dragInitiator;
var dropTarget:IUIComponent = dragEvent.currentTarget as IUIComponent;
var tool:TransformTool = new TransformTool(new ControlSetStandard());
var items:String = dragEvent.dragSource.dataForFormat("items") as String;
var img:Image = new Image();
img.x=50;
img.y=50;
img.width=55;
img.height=55;
img.source=items.toString();
var bitmap:Bitmap= Bitmap(img.content);
var tool:TransformTool = new TransformTool(new ControlSetStandard());
var component:UIComponent = new UIComponent( );
tool.target = img;
tool.x=myCanvas.x;
tool.y=myCanvas.y;
addElement(component);
myCanvas.addChild(img);
img.z=myCanvas.z+1;
component.addChild(tool);
original=new BitmapData(bmd.width,bmd.height,true,0x000000FF);
original.draw(myCanvas);
Just because you added the image to the canvas doesn't mean it has drawn already. Either listen for the updateComplete event on the image or do a callLater to a function that then draws the bitmap.

How to increse performance of raster scrolling on Mac?

I have a game with a big raster map
Now we are using jpeg (4900x4200)
And durring the game we need to scroll through this map.
We use the following:
Class Map extends mx.containers.Canvas
and mx.controls.Image on it
In constructor we have:
public function Map() {
super();
image.source = ResourceManager.interactiveManager.map;//big image
addChild(image);
......
}
for scrolling we are use:
if(parentAsCanvas==null){
parentAsCanvas = (parent as Canvas);
}
parentAsCanvas.verticalScrollPosition = newX;
parentAsCanvas.horizontalScrollPosition = newY;
In windows, we have very good performance.
In Linux and Mac in flashplayer we have a good performance too.
But in browsers performance is quite slow!
What can we do to resolve it?
It's slow because you're rendering a large image all the time.
Here are a few things that cross my mind:
Try using the scrollRect property in a Bimtap object holding your image BitmapData to display just the visible area then use the scrollRect x and y to move to a new region
Try using a BitmapData the size of the viewable area and use copyPixels() to get the right area to display, again using a rectangle
Try using BitmapData.scroll()
Here are a few snippets:
scrollRect:
//assuming map is BitmapData containing your large image
//100x100 is a test scroll area
var scroll:Rectangle = new Rectangle(0,0,100,100);
var bitmap:Bitmap = new Bitmap(map);
bitmap.scrollRect = scroll;
addChild(bitmap);
this.addEventListener(Event.ENTER_FRAME, update);
function update(event:Event):void{
scroll.x = mouseX;
scroll.y = mouseY;
bitmap.scrollRect = scroll;
}
copyPixels:
var scroll:Rectangle = new Rectangle(0,0,100,100);
var scrollPoint:Point = new Point();
var map:BitmapData = new Map(0,0);
var bitmap:Bitmap = new Bitmap(new BitmapData(100,100,false));
bitmap.bitmapData.copyPixels(map,scroll,scrollPoint);
addChild(bitmap);
this.addEventListener(Event.ENTER_FRAME, update);
function update(event:Event):void{
scroll.x = mouseX;
scroll.y = mouseY;
bitmap.bitmapData.fillRect(scroll,0xFFFFFF);
bitmap.bitmapData.copyPixels(map,scroll,scrollPoint);
}
Not perfect, but it should give you an idea
HTH,
George
I've read the following acrticle http://www.insideria.com/2008/04/flex-ria-performance-considera.html
I and i found the resolve of my problem.
If i open my SWF in browser as "http://host/myswf.swf" I have huge performance lose in browser as the reason work LaoyoutManager, that recalculates positions and sizes of all canvases in the application. And it process eats more than 60% of performance capacity.(Yep, we have a lot of Canvases in our application).
And when i putted my application in contant-size html block in html page, all problems were go away! I've got 80% performance increase!

Resources