21

Is it possible to render a scaled image in an ImageView in JavaFX 2.2 without any smoothing applied? I'm rendering a 50x50 image into a 200x200 ImageView, with setSmooth(false), so each pixel in the source image should map to a 4x4 square on the screen.

However, the resulting render still smooths the source pixel across all 16 destination pixels. Does anyone know of a way to do this without manually copying over each pixel into a new image?

fabian
  • 80,457
  • 12
  • 86
  • 114
ajselvig
  • 688
  • 1
  • 5
  • 12

4 Answers4

36

In JavaFX 2.2 ImageView is always going to do some smoothing regardless of the smooth hint you provide to the ImageView.

(Based on testing using Java 7u15 and Windows 7 with an ATI HD4600 graphics card).

Perhaps it is a bug that ImageView will always smooth the Image, but the documentation doesn't really specify exactly what smoothing does or doesn't do, so it's hard to say what its real intent is. You may want to post a reference to this question to the openjfx-dev mailing list or log an issue in the JavaFX issue tracker to get a more expert opinion from a developer.


I tried a few different methods for scaling the Image:

  1. Scale in the Image constructor.
  2. Scale in ImageView with fitWidth/fitHeight.
  3. Scale by using the scaleX/scaleY properties on an ImageView.
  4. Scale by sampling the Image with a PixelReader and creating a new Image with a PixelWriter.

I found that methods 1 & 4 resulted in a sharp pixelated image as you wish for and 2 & 3 resulted in a blurry aliased image.

robot-sampling

Sample code to generate the above output.


Update with ideas on implementing your own image filter

A JavaFX Effect is not the same as the Filter used for the Image loading routines, though an Effect to filter an image could be created. In JavaFX 2.2 publicly documented API to support creation of custom effects, so creating of a custom effect may prove difficult.

The native code for image support was recently open sourced as part of the openjfx project, so you could look at that to see how the filtering is currently implemented.

You may also want to file a feature request against the JavaFX runtime project to "allow us to make our own 2D filters".

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 1
    You always give such impressive answers jewelsea :D – Andy Till Apr 18 '13 at 20:58
  • 1
    Yes, thanks for the detailed answer. I've come to the same conclusion, and ended up implementing #4. The implementation itself is trivial, but I fear that it's much slower than #2 or #3 (although I haven't verified this yet). – ajselvig Apr 19 '13 at 17:53
  • 3
    It's been two years since this answer. Does anyone know if this has been fixed in JavaFX? – Edu Garcia Sep 08 '15 at 05:30
  • I'm not sure what you mean by fixed Edu. My guess is that the system is behaving as the developers intend. If you wish more information on filtering and smoothing images in JavaFX, you can post to the open-jfx developers mailing list as indicated in the answer. – jewelsea Sep 08 '15 at 17:04
  • It is strange, the N° 1. solution just doesn't work on my BMP file, it does not load the image, it works well for the 3 other solutions, I will use the N° 4. solution, thanks. – pdem Jul 19 '16 at 15:25
  • I need sharp pixels in a canvas node (after scaleXY). How can I do that? – Fabricio Aug 05 '16 at 16:31
  • Fabricio, please ask your canvas question as a new question. You can link back to this one to aid potential answerers. – jewelsea Aug 05 '16 at 18:14
6

I know this is a bit older, but I recently had a need for such ImageView, and the following little hack does exactly what I want on my (Windows) machine. No guarantees that it works everywhere.

import com.sun.javafx.sg.prism.NGImageView;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.prism.Graphics;
import com.sun.prism.Texture;
import com.sun.prism.impl.BaseResourceFactory;

import com.sun.prism.Image;
import javafx.scene.image.ImageView;

@SuppressWarnings("restriction")
public class PixelatedImageView extends ImageView {
    @Override protected NGNode impl_createPeer() {
        return new NGImageView() {
            private Image image;

            @Override public void setImage(Object img) {
                super.setImage(img);
                image = (Image) img;
            }

            @Override protected void renderContent(Graphics g) {
                BaseResourceFactory factory = (BaseResourceFactory) g.getResourceFactory();
                Texture tex = factory.getCachedTexture(image, Texture.WrapMode.CLAMP_TO_EDGE);
                tex.setLinearFiltering(false);
                tex.unlock();
                super.renderContent(g);
            }
        };
    }
}

The trick here is that the texture gets re-used, so the linear filtering setting remains "sticky". Why NGImageView couldn't simply pass the "smooth" flag to the texture's linear filtering setting is beyond me however.

Martin Sojka
  • 256
  • 2
  • 7
  • Looks like they don't want you to do this. – Nolan Jun 05 '17 at 19:19
  • 2
    This worked for Java 8, but looks like the functions mentioned here have been renamed and set to private functions in Java 9. Doesn't look like it can be overridden in the same way without significant changes to `ImageView` and `ImageViewHelper`. Anyone figured out how to work around the new restrictions in Java 9? – ddukki Oct 31 '17 at 23:47
  • This answer saved me hours of work ... Why is there an option for smooth=false if JavaFX simply does not give a damn about it. – Arkensor Nov 22 '20 at 16:09
2

No fix for ImageView, but it helped me a lot. After searching for ages, I stumbled upon this post: How can I disable antialiasing on a JavaFX Canvas?

For drawing the image on a canvas, the smoothing can be disabled since JavaFX 12

canvas.getGraphicsContext2D().setImageSmoothing(false);
Myon
  • 937
  • 13
  • 23
  • 1
    There is still some level of smoothing sadly, but it is MUCH less aggressive than regular smoothing. Its relatively easy to make your own "ImageCanvas' class which wraps this and offers the same methods of 'ImageView". – Col-E Aug 12 '23 at 16:07
0

When you add the following constructor to Martin Sojka's answer you can simply pass the javafx Image to the constructor. Also despite the warnings about deprecated functions his answer still works fine (on JDK 1.8_121).

public PixelatedImageView (javafx.scene.image.Image image) {
    super(image);
}
Paul
  • 323
  • 4
  • 5