I'm working on elevation map hierarchy, quad tree, and for that I need to process elevation dataset in form of 16bit grayscale images into different images in various levels. That is for the most part downsampling input data.
Consider input tile image of 14401x10801 in size, which is supposed to be drawn into part of an image 512x512.
Draw call looks something like this:
Graphics2D g2d = (Graphics2D) dstImage.getGraphics();
...
BufferedImage clip = sourceImage.getSubimage(srcX, srcY, srcWidth, srcHeight);
g2d.drawImage(clip , dstX, dstY, dstWidth, dstHeight, null);
where
srcX = srcY = 0
srcWidth = 14401
srcHeight = 10801
dstX = dstY = 0
dstWidth = 171
dstHeight = 128
The area being filled is correct in relative terms, meaning peaks are where they are supposed to be. The issue is, that values do not exactly correspond to what you'd expect. Specifically, pixel 0,0 in dst is definitely sea with zero elevation, yet value 64 is drawn in there.
Looking at contents of src image
sourceImage.getSubimage( 0, 0,
(int)(srcWidth/(double)dstWidth),
(int)(srcHeight/(double)dstHeight));
should yield more or less values from src image that go into pixel 0,0 in dst. These are all zero, so I suppose the issue is not in some peak swaying the value up. What is more perplexing, all pixels that should be zero are set to 64.
For purpose of this processing (as java cannot handle negative values in 16 bit grayscale) all pixels are elevated by 16384. Elevating these by mere 1000 results in error -229 instead of 64. Value change is simple "add to all inputs" -> "process" -> "subtract from all outputs".
I hoped to avoid all the resampling, clipping, and efficiency problems by simply doing all the work over images, but so far it seems java does some obscure color mapping.
Any ideas what I might be doing wrong?
So I poked the code around a bit, and out of sole desperation started turning off things that should not, but might, have an impact. The culprit turned out to be
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
This hint does not ... hint ... how on earth one could get from average over 84 times 16384 something else than 16384, but hey, who am I to judge.
Documentation for this hint states
The RENDERING hint is a general hint that provides a high level
recommendation as to whether to bias algorithm choices more for speed
or quality when evaluating tradeoffs. This hint could be consulted for any rendering or image manipulation operation, but decisions will usually honor other, more specific hints in preference to this hint.
not very helpful either.
I did already encounter some discouragement from use of rendering hints in java2d, and it seems there indeed is something about that.
Related
I was trying to implement a color picking system by using a PBO(pixel buffer object using OpenGL), and when I finished, I realized the numbers that came out of the PBO when mapped didn't make any sense at all. I made my application render big squares of different colors and the result DID change between these different colors, but even after analyzing the numbers, I can't make sense of it.
For example, clicking on pure red gave me bytes of
(-1,0,0), while pure blue gave (0,0,-1), but against all logic, pure green gives (-1,0,-1), cyan also gives (-1,0,0), and yellow gives (76,-1,0).
Obviously these numbers are wrong, given that two different colors can result in the same byte formation. Shouldn't a fully red color be (127,0,0)?
Here is the code I used for initialization, size 3 because I am only reading one pixel.
pboid = glGenBuffersARB(); //Initialize buffer for pbo
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, pboid); //bind buffer
glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, 3,GL_DYNAMIC_READ); //create a pbo with 3 slots
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0); //unbind buffer
And here is the code I used for reading the pixels
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, pboid); //Bind the pbo
glReadPixels((int)lastMousePosition.x,(int)lastMousePosition.y,1,1, GL_RGB, GL_UNSIGNED_BYTE, 0); //Read 1 pixel
ByteBuffer colorBuffer = glMapBufferARB(GL_PIXEL_PACK_BUFFER_EXT, GL_READ_ONLY_ARB); //Map the buffer so we can read it
for(int x = 0; x < colorBuffer.limit(); x++)
{
System.out.println("color byte: " + colorBuffer.get(x)); //Print out the color byte
}
glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_EXT); //Unmap the buffer so it can be used again
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0); //Unbind the pbo
If I am wrong in any assumptions I have made, please correct me. I am planning perhaps to use this system to tell which gui element is being clicked by rendering each of them to an fbo with a unique color and testing which pixel color was clicked on. Thanks in advance for anyone who can help!
At long last I have finally found the issue!
Firstly, using Byte.toUnsignedInt(byte), you can turn the color that the pbo gives you into your traditional 0-255 range rgb numbers.
Secondly, this being the primary issue, when OpenGL asks for pixel coordinates to fill a pbo, it is relative to bottom right. My issue was that I was using GLFW which gives coordinates relative to top right, meaning color picking in the vertical middle of the screen was accurate, but that it was getting the inverse part of the screen I was looking for when color picking elsewhere. To fix this, simply subtract the mouse click's y coordinate from the window height.
Thanks for the help and ideas!
There are a couple of possibilities, but I don't have my openGL system set up to test - but you can try these anyhow. Also I don't know Java too well ( C,C++ etc is my domain)
EDIT
1) You have asked for GL_UNSIGNED_BYTE data from glReadPixels(), but you are printing out in signed format. GL_UNSIGNED_BYTE has values 0-256, so negative values are not possible! Try to format you printout for UNSIGNED_BYTE and see where that leads. (from your code I can see this is now fixed).
2) As derhass pointed out in his comments, you should not be using the ARB (architecture Review Board) extension versions of OpenGL buffer functions since these are part of OpenGL core for quite a long time now. See https://www.khronos.org/opengl/wiki/History_of_OpenGL for version history. From this I can see glBindBufferARB (for example) was deprecated in 2003. It may or not impact your particular problem, but replace glXXXXXARB() with glXXXXX() thorughout, and make sure your OpenGL libraries are recent (v4 or later).
3) Also credit derhass, and reading your GitHub code, your getMousePosition() via glfwGetCursorPos returns screen coordinates (0,0 is top left of your window) so you need to convert to viewport coordinates (0,0 is bottom left) to read the framebuffer. Your code at GitHub seems not to be making the conversion.
4) Also credit derhass, you dont' need to use PBO at all for basic color picking. glReadPixels() default target is the framebuffer, so you can safely dispense with the VBO and get color, depth and stencil data directly from the framebuffer. (you need to enable the depth and stencil buffers).
5) If you are selecting on a 3D scene, you will also need to convert (unproject) the viewport coordinates and depth back to worldcoordinates to be able to identify which object you have clicked on. See https://en.wikibooks.org/wiki/OpenGL_Programming/Object_selection for some ideas on selection.
I hope all this helps a bit, although it feels a like learning experience for both of us.
I want to have a pixel-styled look for my test game, but when I scale them to the size I want them to be displayed at (50x50; the actual size of the tiles is 16x16), it gets that ugly smoothed out look I dont want to have.
img = new Image("test.png").getScaledCopy(50,50);
Size of test image is 16x16 and I want it to be displayed at a size of 50x50, but like 1:1 scaled, not the ugly smoothed out way.
A bit like in the sandbox game "Minecraft", the block tiles and such are much smaller than the displayed ones, yet they are not "smoothed".
Thank you a lot for any help :)
In your case, the anti aliasing filters look crummy because you have a very small image (16x16) and are upscaling it only a small bit (to 50x50). In your case, you likely want a sharper image, so you'd likely want to go with nearest neighbor interpolation, rather than the built-in default (either bi-linear or bi-cubic, not entirely sure which is the default).
Image original = …;
original.setFilter(Image.FILTER_NEAREST);
Image scaled = original.getScaledCopy();
So in your case, it would be something like:
Image original;
Image scaled;
original = new Image("test.png");
original.setFilter(Image.FILTER_NEAREST);
scaled = original.getScaledCopy(50, 50);
Please note that this won't be a true "1:1" scaling, since the scaling factor is not an integer ratio (ie: 50/16 isn't a whole non-decimal number). If you were to upscale to 64x64, you could have a "1:1" rescaling, since 16*k=64, where k is an integer.
I would like to resize a Java BufferedImage, making it smaller vertically but without using any type of averaging, so that if a pixel-row is "blank" (white) in the source image, there will be a white pixel-row in the corresponding position of the destination image: the "min" operation. The default algorithms (specified in getScaledInstance) do not allow me a fine-grained enough control. I would like to implement the following logic:
for each pixel row in the w-pixels wide destination image, d = pixel[w]
find the corresponding j pixel rows of the source image, s[][] = pixel[j][w]
write the new line of pixels, so that d[i] = min(s[j][i]) over all j, i
I have been reading on RescaleOp, but have not figured out how to implement this functionality -- it is admittedly a weird type of scaling. Can anyone provide me pointers on how to do this? In the worse case, I figure I can just reserve the destination ImageBuffer and copy the pixels following the pseudocode, but I was wondering if there is better way.
The RescaleOp methods include a parameter called RenderingHints. There is a hint called KEY_INTERPOLATION that decides the color to use when scaling an image.
If you use the value VALUE_INTERPOLATION_NEAREST_NEIGHBOR for the KEY_INTERPOLATION, Java will use the original colors, rather than using some type of algorithm to recalculate the new colors.
So, instead of white lines turning to gray or some mix of color, you'll get either white lines, or you won't get any lines at all. It all depends on the scaling factor, and if it's an even or odd row. For example, if you are scaling by half, then each 1 pixel horizontal line has at least a 50% change of appearing in the new image. However, if the white lines were two pixels in height, you'd have a 100% chance of the white line appearing.
This is probably the closest you're going to get besides writing your own scaling method. Unfortunately, I don't see any other hints that might help further.
To implement your own scaling method, you could create a new class that implements the BufferedImageOp interface, and implement the filter() method. Use getRGB() and setRGB() on the BufferedImage object to get the pixels from the original image and set the pixels on the new image.
I'm a reasonably experienced Java programmer but relatively new to Java2D. I'm trying to scale an image but I'm getting poor quality results. The image is a preview of a panel so contains things like text and textfields. I'll always be scaling down, never up.
Currently I'm using the following code:-
g.drawImage(panelImage, 0, 0, scaledWidth, scaledHeight, null);
Where panelImage is the full sized preview (BufferedImage) and scaledWidth and scaledHeight are the respective target dimensions. I seem to lose a lot of detail in the text and edges of things like textfields etc.
Is there a better call I should be using to scale the image?
Thanks,
John
A suggestion I can make is to first resize the image onto a separate BufferedImage. The reason being, a Graphics2D object of the BufferedImage can be obtained in order to produce a better quality scaled image.
Graphics2D can accept "rendering hints" which instruct the way image processing should be performed by the Graphics2D object. The setRenderingHint method is one of the methods which can be used to set those rendering hints. The rendering hints from the RenderingHints class can be used.
Then, using that Graphics2D object, an image can be drawn to the BufferedImage using the rendering hints specified earlier.
A rough (untested) code would work as the following:
BufferedImage scaledImage = new BufferedImage(
scaledWidth,
scaledHeight,
BufferedImage.TYPE_INT_RGB
);
Graphics2D g = scaledImage.createGraphics();
g.setRenderingHints(
RenderingHints.Key.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC
);
g.drawImage(panelImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
Other rendering hints of interest may include:
KEY_ANTIALIASING
KEY_RENDERING
The Controlling Rendering Quality section of The Java Tutorials also has more information on how to control the rendering quality of Graphics2D objects.
And for a very good source of information on dealing with graphical interfaces in general, Filthy Rich Clients by Chet Haase and Romain Guy is highly recommended. There is one section of the book that deals with the issue of scaling images, which seems quite relevant.
May be you should call:
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
and
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Coobird is right that you need to incrementally down-scale (preferably using BICUBIC) to get a good-looking result for a small enough thumbnail. Image.getScaledInstance used to do this with the AREA_AVERAGED approach, but it is much slower than the incremental downscale that was original proposed by Chris Campbell in his "Perils of Image.getScaledInstance()" article.
Please excuse the self-promotion here, but I rolled a handful of "native Java best practices" when it comes to image scaling into a library called imgscalr.
It's available under an Apache 2 license and source is all on GitHub; the goal of the library was to make image-scaling in native Java dead-easy (1 class, 5 static methods) and ensure the best looking result (which is what you originally wanted), the fastest result (great when scaling among large images) or a balance between the two and let the library decide which one to use.
I just wanted a lib that could "Resize my image and get out of my way" and after reading all these posts for a few days as I addressed the pain point (my own as well) just was circling back and sharing the work for anyone else it may help.
Coobird has the correct idea. I would also try RenderingHints.VALUE_INTERPOLATION_BILINEAR interpolation and see if it's nicer looking. Bicubic works better when upscaling, though. For the best results when downscaling, one should downscale in several steps. First halve the resolution, then halve again, etc. until you get near the desired resolution (i.e. you cannot halve or the image will get too small). Final step is to scale down to the desired resolution.
For example, let's say your input image is 800x600 and you want downscale to 160x120:
Downscale 50%. --> 400x300
Downscale 50%. --> 200x150
Downscale to 160x120.
What's the easy way to render a shape in Java to its "actual size". For example, I have a tube with a diameter of 1" and I want to depict it on screen as the outline of a 1" circle. The Graphics2D method drawOval(int x, int y, int width, int height) takes a height and width in pixels. What are the steps to translate a pixel size into the size rendered on screen?
Thanks in advance.
The getNormalizingTransform() method of the class GraphicsConfiguration looks like it has some potential
http://java.sun.com/javase/6/docs/api/java/awt/GraphicsConfiguration.html#getNormalizingTransform()
The java.awt.Toolkit will tell you the size of a pixel, and the pixel dimensions of the screen. This is based on information from the underlying system, however, which may sometimes be misconfigured.
So, to draw a 1" circle, you'd use a diameter of 1.0 * tk.getScreenResolution(), a 2.5" circle is 2.5 * tk.getScreenResolution(), etc.
You should be aware that, even though you might be able to find out about the screen size and resolution, you still can't be sure of the actual size of the displayed picture. If the user has a CRT screen, the screen is likely to be a bit smaller than the actual screen size.
Therefore, if you really need accurate results, the only way is to let the user adjust a ruler displayed on the screen interactively and compare it with an actual ruler.
In theory you can do it this way. The java.awt.Toolkit will tell you the size of a pixel, and the pixel dimensions of the screen. So, to draw a 1" circle, you'd use a diameter of 1.0 * tk.getScreenResolution(), a 2.5" circle is 2.5 * tk.getScreenResolution(), etc. Or you can use the GraphicsConfiguration.getNormalizingTransform() method which adjusts the resolution to a 'fixed' size.
Unfortunately both of these methods rely on the underlying system knowing (and telling you) the actual resolution of your screen. In practice this very rarely occurs. All sorts of things can affect the actual size of a pixel. The actual size and make of monitor is one, and some monitors even allow you to adjust the size of the image on the screen.
This article http://www.developer.com/java/other/print.php/626071 discusses this.
Printers are generally better at telling you their real resolution. If you absolutely must have a picture which is the correct size, send it there.
Acknowledgements to the various answers from which I synthesized this one.
The problem you're going to have is Pixels are not always the same size. For example a 100 x 100 pixel square is going to be different sizes on a 17" 1280 x 1024 monitor and a 19" 1280 x 1024 monitor.
I don't believe there is an API which tells you the physical size of a display.
Asking the user the size of their monitor might not help as a lot of people simply won't know.
You could display a number of lines on screen and get the user to click which one is closest to 1 inch and scale all your rendering to that, but it's a bit clumsy.
Well, you would need the size of the monitor and resolution. Let's say the monitor is 17" with a 1280:1024 aspect ratio.
The screen size is the hypotenuse, so you would need to find the number of pixels on the hypotenuse. Simple geometry later, you can get the pixels/inch calculation.
1280px^2 + 1024px^2 = c^2, c ~= 1639.2px. So it's 1639.2px/17inch = 96.4px/inch for a 17 inch monitor with 1280x1024 resolution. This would have to be entered by the user.
I was under the impression that simply getting the screen resolution from Toolkit was not enough. I think you need to do something more along the lines of
float scalingFactor = Toolkit.getDefaultToolkit().getScreenResolution() / 72f;
Making a 1" square
int width = 1.0 * scalingFactor;
and a 2.5" square
int width = 2.5 * scalingFactor;
All of this being that Java2D assumes a 72 dpi screen resolution, and if the system is set differently you need to scale up to correct for this.
This is a curious question, one I haven't thought of. Off the top of my head, you would probably need to know a combination of screen size (17", 21", etc.), screen resolution ("800x600, 1280x1024, etc.) and DPI of the screen (72, 96, 120, etc.).
Through various api's, you can determine the screen resolution, and maybe the dpi... but good luck with the screen size. And even with all that, you're still not guaranteed to produce the correct size on screen.