I am trying to write a SWT component, that is able to take and draw an instance of java.awt.BufferedImage. My problem is that SWT's Image and AWT's BufferedImage are incompatible: SWT components can't draw java.awt.Image, AWT/Swing components can't draw org.eclipse.swt.graphics.Image.
There are several approaches that try to solve this problem in other ways (which also may have some variations, but basically there are these two):
Convert between SWT Image and AWT BufferedImage
Swing/SWT Integration
They all have shortcomings and didn't satisfy my expectations:
The first approach, to convert an SWT Image to a BufferedImage, results in poor performance for large images due to the creation of a new RGB instance for every Pixel.
The second approach has several shortcomings in usability. See the "workarounds" at the end of the linked article.
This lead to the conclusion that I'd try my best to write a component (based on org.eclipse.swt.widgets.Canvas or org.eclipse.swt.widgets.Composite) which allows to draw a BufferedImage directly without any conversion of images.
My approach was to draw it pixel by pixel. Therefore I simply had to get an instance of GC, walk the source BufferedImage line by line, left-to-right and drawing the corresponding Color using GC.setForeground(Color color) and GC.drawPoint(int x, int y).
First, I created a new instance of Color for every pixel, which uses quite a lot of memory and adds an additional delay, since new Color acquires system resources and creating a new object for every pixel also takes its time.
Then I tried to pre-load all possible (24 bit) Colors into an array before drawing the image. This lead to an explosion of memory usage (>= 600 MB), which was clear before I was trying it, but I had to verify it.
Caching only the used Colors also lead to more memory consumption than would have been required.
I think there has to be a more low-level approach that doesn't require that much memory, since SWT is able to draw whole (SWT) Images without consuming that much memory.
I would appreciate any ideas or solutions.
I found out there's a way to "convert" an BufferedImage to an Image by using the original image's data buffer if it is 24 bit RGB. This is possible, since the image formats are compatible.
final BufferedImage original = ImageIO.read(new File("some-image.jpg");
final PaletteData palette =
new PaletteData(0x0000FF, 0x00FF00, 0xFF0000);
// the last argument contains the byte[] with the image data
final ImageData data = new ImageData(original.getWidth(), original.getHeight(),
24, palette, 4,
((DataBufferByte) original.getData().getDataBuffer()).getData());
final Image converted = new Image(getDevice(), data);
This way, one doesn't have to create thousands of new objects. This approach comes with the disadvantage that one needs to ensure that the original image is of type RGB 24 bit. Otherwise the image has to be converted to this format.
After that, an image can be drawn with the following code:
// get the GC of your component
gc.drawImage(image, 0, 0);
Probably other bit depths can be converted in a similar way, but this is all I need for the moment.
Related
I have a lot of images that taken by my Digital camera with very high resolution 3000 * 4000 and it takes a lot of Hard disk space, I used Photoshop to open each Image and re-size it o be with small resolution, but it needs a lot of time and effort
I think that I can write simple program that open the folder of images and read each file and get it's width and height and if it's very high change it and overwrite the image with the small one.
Here some code I use in a Java-EE project (should work in normal application to:
int rw = the width I needed;
BufferedImage image = ImageIO.read(new File(filename));
ResampleOp resampleOp = new ResampleOp(rw,(rw * image.getHeight()) / image.getWidth() );
resampleOp.setFilter(ResampleFilters.getLanczos3Filter());
image = resampleOp.filter(image, null);
File tmpFile = new File(tmpName);
ImageIO.write(image, "jpg", tmpFile);
The resample filter comes from java-image-scaling library. It also contains BSpline and Bicubic filters among others if you don't like the Lanczos3. If the images are not in sRGB color space Java silently converts the color space to sRGB (which accidentally was what I needed).
Also Java loses all EXIF data, thought it does provide some (very hard to use) methods to retrieve it. For color correct rendering you may wish to at least add a sRGB flag to the file. For that see here.
+1 to what some of the other folks said about not specifically needing Java for this, but I imagine you must have known this and were maybe asking because you either wanted to write such a utility or thought it would be fun?
Either way, getting the image file listing from a dir is straight forward, resizing them correctly can take a bit more leg work as you'll notice from Googling for best-practices and seeing about 9 different ways to actually resize the files.
I wrote imgscalr to address this exact issue; it's a dead-simple API (single class, bunch of static methods) and has some good adoption in webapps and other tools utilizing it.
Steps to resize would look like this (roughly):
Get file list
BufferedImage image = ImageIO.read(files[i]);
image = Scalr.resize(image, width);
ImageIO.write(image);
There are a multitude of "resize" methods to call on the Scalr class, and all of them honor the image's original proportions. So if you scale only using a targetWidth (say 1024 pixels) the height will be calculated for you to make sure the image still looks exactly right.
If you scale with width and height, but they would violate the proportions of the image and make it look "Stretched", then based on the orientation of the image (portrait or landscape) one dimension will be used as the anchor and the other incorrect dimension will be recalculated for you transparently.
There are also a multitude of different Quality settings and FIT-TO scaling modes you can use, but the library was designed to "do the right thing" always, so using it is very easy.
You can dig through the source, it is all Apache 2 licensed. You can see that it implements the Java2D team's best-practices for scaling images in Java and pedantically cleans up after itself so no memory gets leaked.
Hope that helps.
You do not need Java to do this. It's a waste of time and resources. If you have photoshop you can do it with recording actions: batch resize using actions
AffineTransformOp offers the additional flexibility of choosing the interpolation type, as shown here.
You can individually or batch resize with our desktop image resizing application called Sizester. There's a full functioning 15-day free trial on our site (www.sizester.com).
I am wondering is there a way to convert Image to BufferedImage without code like a
new BufferedImage(...)
because every new init makes app run slower , moreover, if it is in paint() method :(
Please advise the most optimal conversion way.
Thanks
No. Not unless the original Image happens to be a BufferedImage already. Then you can just do a cast:
BufferedImage bufImg = null;
if (origImage instanceof BufferedImage) {
bufImg = (BufferedImage) origImage;
else {
bugImg = new BufferedImage(...);
// proper initialization
}
If it's not a BufferedImage it may very well be for instance a VolatileImage (the other concrete subclass in the API).
From the docs on volatile image:
VolatileImage is an image which can lose its contents at any time due to circumstances beyond the control of the application (e.g., situations caused by the operating system or by other applications).
As you may understand, such image can not provide the same interface as a BufferedImage, thus the only way to get hold of a BufferedImage is to create one, and draw the original image on top of it.
because every new init makes app run slower
Cache one BufferedImage, then only create a new image if the required size changes. Otherwise clear the Graphics object of the current instance and do whatever new drawing is needed.
Is there a way to draw a BufferedImage to JLabel with the paint() method?
One convenient approach is to implement the Icon interface. In this example, Histogram simply draws itself when the label is told to repaint().
If the source of the image requires a time-consuming operation such as scaling, pre-render the image as shown in the static factory, GradientImage.
I need to translate colors in bitmap loaded to BufferedImage from RGB to YCbCr (luminance and 2 channels chrominance) and back after process.
I made it with functions used like rgb2ycbcr() in main method for each pixel, but it isn't so smart solution. I should use ColorSpace and ColorModel classes to get BufferedImage with correct color space. It would be more flexible method, but I don't know how to do that.
I'm lost and I need some tips. Can somebody help me?
As I understood your question, you want to do the following:
Load RGB image -> process YCbCr image -> Use RGB image again
And you want us to help you, to make this process as seamless as possible. First and foremost you want us to give you a simple way to avoid the -> (converting) parts.
Well I looked into the BufferedImage documentation. It seems, as if there doesn't exist a way to change the ColorSpace of an once created BufferedImage.
You could create a new BufferedImage with an YCbCr color space for that you can use the predefined ICC_ColorSpace. Then you copy the data from your old image possibly via ColorSpace.fromRGB to the YCbCr color space, do the image processing and then convert again via ColorSpace.toRGB. This method requires you to fully convert the image before and after processing via existing methods. Furthermore you have to know, how ICC_ColorSpace converts your image to YCbCr color space. Otherwise you can't know, which array indices corresponds to the same pixel.
If you just want to create a wrapper around the RGB-BufferedImage that lets you manipulate this image, as if it was an YCbCr image, that isn't possible with BufferedImage.
EDIT:
To convert the color space of a BufferedImage use ColorConvertOp. The code would look something like this:
ColorConvertOp cco = new ColorConvertOp(new YCbCrColorSpace(), null);
BufferedImage ycbcrImage = cco.filter( oldRGBImage, null );
This requires you to either write your own ColorSpace class or you could download and use the classes mentioned here. If you just want to load a JPEG image you should use the predefined classes.
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.