I am using the program ImageResizer with the XBR4x algorithm to upscale .gif images from an old 2D game from 32x32 to 48x48.
The exact procedure:
Manually rename all images to .jpeg because the program wont open .gif
Resize the images, they are saved by the program as .bmp
Manually rename the images to .gif again.
The problem:
When looking at the images in Paint they look very good, when drawn in my RGB BufferedImage they suddenly all have a white/grey ~1px border which is not the Background Color, the images are placed directly next to each other. As I have a whole mosaic of those images the white borders are a no go.
Image 32x32:
Image 48x48 after upscaling:
Ingame screenshot of 4 of those earth images with white borders:
The question:
How do those borders originate? And if not possible to answer this, are there more reliable methods of upscaling low resolution game images making them look less pixelated?
I think that is an artifact of the image resizing algorithm, the borders are actually visible one the upscaled image before it is combined, if you look at them in XnView, for example.
The best way to fix that would be to use another tool to resize the image, one which allows the user to control such borderline effects, but if you have to use this one you could still work around the problem by constructing a 3x3 grid of the original image (which would be 96x96), scaling it up to 144x144 and then cutting out the central 48x48 piece. This would eliminate the borderline effects.
The border is a result of a scaling procedure performed by the mentioned tool. Consider this demo that shows tiles based on scaled image from the question and scaled image created using Image.getScaledInstance().
Note that if you choose to stay with your own scaling method check out The Perils of Image.getScaledInstance() for more optimized solutions.
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class TestImageScale {
public static void main(String[] args) {
try {
BufferedImage original = ImageIO.read(new URL(
"http://i.stack.imgur.com/rY2i8.gif"));
Image scaled = original.getScaledInstance(48, 48,
Image.SCALE_AREA_AVERAGING);
BufferedImage scaledOP = ImageIO.read(new URL(
"http://i.stack.imgur.com/Argxi.png"));
BufferedImage tilesOP = buildTiles(scaledOP, 3, 3);
BufferedImage tiles = buildTiles(scaled, 3, 3);
JPanel panel = new JPanel();
panel.add(new JLabel(new ImageIcon(tilesOP)));
panel.add(new JLabel(new ImageIcon(tiles)));
JOptionPane.showMessageDialog(null, panel,
"Tiles: OP vs getScaledInstance",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage(), "Failure",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
static BufferedImage buildTiles(Image tile, int rows, int columns) {
int width = tile.getWidth(null);
int height = tile.getHeight(null);
BufferedImage dest = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.createCompatibleImage(width * rows, height * columns,
Transparency.TRANSLUCENT);
Graphics g = dest.getGraphics();
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
g.drawImage(tile, row * width, col * width, null);
}
}
g.dispose();
return dest;
}
}
Just a wild guess: Do the original images have an Alpha channel (or do you implicitly create one when resizing)? When resizing an image with alpha, the scaling process may assume the area outside the image to be transparent and the border pixels may become partially transparent, too.
I emailed Hawkynt, the developer of the tool and it seems the error is not in the tool but in Microsofts implementation and he fixed it (actually even bigger tools like Multiple Image Resizer .NET have the problem). This is what he said about his program:
"When you entered width and/or height manually, the image got resized by the chosen algorithm where everything went fine.
Afterwards I used the resample command from GDI+ which implements a Microsoft version of the bicubic resize algorithm.
This implementation is flawed, so it produces one pixel on the left and upper side for images under 300px.
I fixed it by simply making the resized image one pixel larger than wanted and shifting it to the left and up one pixel, so the white border is no longer visible and the target image hast he expected dimensions."
Related
I want to make an application with a small jLabel(50x50) in its corner.
The Problem I now have is that the Image the Label displays is looking really bad.
I also added the same Image as an Icon to a shortcut in windows on my desktop just as a comparison.
Windows on the left side and Java JLabel on the right.
How can I archive a similar scaling result in Jave with no loss in quality?
It does not need to use JLabel.
Code:
ImageIcon imgIcon = new ImageIcon(path);
Image img = imgIcon.getImage();
Image imgScaled = img.getScaledInstance((int) (getWidth()), (int) (getHeight()),
Image.SCALE_SMOOTH);
ImageIcon image = new ImageIcon(imgScaled);
label.setIcon(image);
EDIT:
If you look at these Google Chrome Icons, they are extremely tiny but still sharp and high resolution, how can I archive this in Java?
Your can to use in BufferedImage, this is much higher resolution the JLabel.
You can to create BufferedImage from .png file with ImageIO class.
I see two options, or maybe a combination of this:
You're using a weird resolution image for your ImageIcon
Ratio of width to height is not equal, thus skewed scaling
EDIT In case 2, make sure the JComponent you're using to fetch dimensions from (the one you're calling getWidth and getHeight on) has equal dimensions for both width and height.
I cut your left image, at 62px width/height. First row shows that image scaled, second row shows what happens when I scale the source image down to 32px in graphics program first:
Dimensions, as you can see below, go from 62px up by increments of 10px. Code was run on Java 1.8, Windows 10:
void addSeries(Image srcImg, JPanel targetPanel) {
for (int i = 0; i < 50; i += 10) {
int dimension = 62 + i;
Image imgScaled = srcImg.getScaledInstance(dimension, dimension, Image.SCALE_SMOOTH);
ImageIcon scaledIcon = new ImageIcon(imgScaled);
JLabel label = new JLabel();
label.setIcon(scaledIcon);
targetPanel.add(label);
}
}
I'm trying to code a class to seam carve images in x and y direction. The x direction is working, and to reduce the y direction I thought about simply rotating the image 90° and run the same code over the already rescaled image (in x direction only) and after that, rotate it back to its initial state.
I found something with AffineTransform and tried it. It actually produced a rotated image, but messed up the colors and I don't know why.
This is all the code:
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.io.File;
import java.io.IOException;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
public class example {
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException {
// TODO code application logic here
BufferedImage imgIn = ImageIO.read(new File("landscape.jpg"));
BufferedImage imgIn2 = imgIn;
AffineTransform tx = new AffineTransform();
tx.rotate(Math.PI/2, imgIn2.getWidth() / 2, imgIn2.getHeight() / 2);//(radian,arbit_X,arbit_Y)
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
BufferedImage last = op.filter(imgIn2, null);//(sourse,destination)
ImageIO.write(last, "JPEG", new File("distortedColors.jpg"));
}
}
Just alter the filename in
BufferedImage imgIn = ImageIO.read(new File("landscape.jpg")); and try it.
When executed, you get 4 images: a heatmap, an image with seams in it and a rescaled image. The last image is a test to see if the rotation worked and it should show a rotated image but with distorted colors...
Help would be greatly appreciated!
EDIT:
The problem is with the AffineTransformOp You need :
AffineTransformOp.TYPE_NEAREST_NEIGHBOR
instead of the BILINEAR you have now.
Second paragraph from documentation hints towards this.
This class uses an affine transform to perform a linear mapping from
2D coordinates in the source image or Raster to 2D coordinates in the
destination image or Raster. The type of interpolation that is used is
specified through a constructor, either by a RenderingHints object or
by one of the integer interpolation types defined in this class. If a
RenderingHints object is specified in the constructor, the
interpolation hint and the rendering quality hint are used to set the
interpolation type for this operation.
The color rendering hint and
the dithering hint can be used when color conversion is required. Note
that the following constraints have to be met: The source and
destination must be different. For Raster objects, the number of bands
in the source must be equal to the number of bands in the destination.
So this works
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
It seems like there's a color conversion happening due to passing null to op.filter(imgIn2, null);.
If you change it like that it should work:
BufferedImage last = new BufferedImage( imgIn2.getWidth(), imgIn2.getHeight(), imgIn2.getType() );
op.filter(imgIn2, last );
Building on what bhavya said...
Keep it simple and you should use the dimensions expected from the operation:
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
BufferedImage destinationImage = op.filter(bImage, op.createCompatibleDestImage(bImage, null));
I think I've just pinpointed the problem, but I'm still not sure what to do about it.
I've created various animated gifs using Photoshop and I wanted to display them in my java application. Using ImageIcon, some of them display as they should. Those are the ones with frames that have the same frame rate (such as each frame is 1 second long). However, the gifs that have varying frame rates (ex. one frame is 1 second, the other is .5 seconds, the next is .2) doesn't seem to be showing correctly. Once the gif reaches the frames that vary in seconds the image messes up. Some examples include the background briefly changing color and half of the image disappearing, or most of the image disappearing with the gif still animating.
I'm having a difficult time articulating, but do I need to use something else other than ImageIcon to correctly load the gif with varying frame rates? Like something with BufferedImage?
Edit: Added complete code below. Again, it works for the image with equal frame rates, but not for one with varying frame rates.
here is the image that works fine
and here is the image that messes up
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JWindow;
public class Creature extends JWindow {
public void initCreature() {
JPanel contentPane = (JPanel) getContentPane();
ImageIcon idleImage = new ImageIcon(getClass().getResource("img.gif"));
// Set window properties
getRootPane().putClientProperty("Window.shadow", false);
setBackground(new Color(0,0,0,0));
setAlwaysOnTop(true);
contentPane.setOpaque(false);
// Get size of user's screen
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
Rectangle screen = defaultScreen.getDefaultConfiguration().getBounds();
int taskbarheight = (int) (Toolkit.getDefaultToolkit().getScreenSize().height
- GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getHeight());
int x = (int) screen.getMaxX() - idleImage.getIconWidth();
int y = (int) screen.getMaxY() - idleImage.getIconHeight() - taskbarheight;
JLabel imageLabel = new JLabel(idleImage);
setSize(idleImage.getIconWidth(), idleImage.getIconHeight());
contentPane.add(imageLabel);
setLocation(x, y);
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
Creature cr = new Creature();
cr.initCreature();
}
});
}
}
The frame rates seem the same in both versions (one seen in a browser, the other in a JLabel). The actual problem is that the rendering in Java becomes 'clipped' in the areas of the animation that do not change.
At a guess I'd say the problem is that PhotoShop is using an advanced encoding to ensure that only the parts of the frame that change, are updated. Note that although Java supports a particular file type like GIF, PNG or JPEG, does not mean it correctly understands every encoding type for each file type.
A simpler image that, for every change, changes the entire frame should work better, but also be larger in bytes. In fact, you can see that working in the other image. Because the little dragon is hunching down then rising back up, all parts of the visible image need to change, so the animation of that cannot be optimized in the same way as the first image can.
The imageLabel doesn't know to repaint when the GIF's frame changes. You need to tell the ImageIcon to notify the JLabel of frame changes, by passing the JLabel to setImageObserver:
idleImage.setImageObserver(imageLabel);
From that method's documentation:
Set this property if the ImageIcon contains an animated GIF, so the observer is notified to update its display.
I have been struggling to find and answer to this issue. I am trying to change the color of a pixel in a large BufferedImage with the imageType of TYPE_BYTE_BINARY. By default when I create the image it will create a black image which is fine but I cannot seem to be able to change pixel color to white.
This is the basic idea of what I want to do.
BufferedImage bi = new BufferedImage(dim[0], dim[1], BufferedImage.TYPE_BYTE_BINARY);
bi.setRBG(x, y, 255)
This seems weird to me as a TYPE_BYTE_BINARY image will not have RGB color, so I know that that is not the correct solution.
Another idea that I had was to create multiple bufferedImage TYPE_BYTE_BINARY with the createGraphics() method and then combine all of those buffered images into one large bufferedImage but I could not find any information about that when using the TYPE_BYTE_BINARY imageType.
When reading up on this I came across people saying that you need to use createGraphics() method on the BufferedImage but I don't want to do that as it will use up too much memory.
I came across this link http://docs.oracle.com/javase/7/docs/api/java/awt/image/Raster.html specifically for this method createPackedRaster()(the second one). This seems like it might be on the right track.
Are those the only options to be able to edit a TYPE_BYTE_BINARY image? Or is there another way that is similar to the way that python handles 1 bit depth images?
In python this is all that needs to be done.
im = Image.new("1", (imageDim, imageDim), "white")
picture = im.load()
picture[x, y] = 0 # 0 or 1 to change color black or white
All help or guidance is appreciated.
All works. I am able to get a white pixel on the image.
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.awt.Color;
import java.io.File;
public class MakeImage
{
public static void main(String[] args)
{
BufferedImage im = new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY);
im.setRGB(10, 10, Color.WHITE.getRGB());
try
{
ImageIO.write(im, "png", new File("image.png"));
}
catch (IOException e)
{
System.out.println("Some exception occured " + e);
}
}
}
I have some java code that needs to programmatically render text onto an image. So I use BufferedImage, and write text on the Graphics object.
However, when configuring the font instance, one would specify the font size in points. When a piece of text is rendered onto an image, AWT will translate the points into pixels, based on the resolution of the Graphics object. I don't want to get myself involved in computing the pixel/point ratio, since it's really the task for the AWT. The image that is being produced is for a high resolution device (higher than any desktop monitors).
But, I don't seem to find a way to specify what the resolution of the Graphics is. It inherits it from the local graphics environment, which is beyond my control. I don't really want this code to be dependent on anything local, and I'm not even sure it's "sane", to use local graphics environment for determining the resolution of off screen rasters, who knows what people would want them for.
So, any way I can specify the resolution for an off screen image of any kind (preferably the one that can create Graphics object so I can use standard AWT rendering API)?
(update)
Here is a (rather long) sample problem that renders a piece of text on an image, with predefined font size in pixels (effectively, the target device DPI is 72). What bugs me, is that I have to use local screen DPI to make the calculation of the font size in points, though I'm not using the screen in any way, so it's not relevant, and plain fails on headless systems all together. What I would loved in this case instead, is being able to create an off screen image (graphics, raster), with DPI of 72, which would make points, by value, be equal to pixels.
Sample way to run the code:
$ java FontDisplay Monospace 150 "Cat in a bag" 1.png
This would render "Cat in a bag message", with font size of 150 pixels, on a 150 pixel tall image, and save the result in 1.png.
import java.awt.*;
import java.awt.image.*;
import java.awt.font.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import java.io.*;
import java.util.*;
public class FontDisplay {
public static void main(String a[]) throws Exception {
// args: <font_name> <pixel_height> <text> <image_file>
// image file must have supported extension.
int height = Integer.parseInt(a[1]);
String text = a[2];
BufferedImage bi = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
System.out.println("dpi : "+dpi);
float points = (float)height * 72.0F / (float)dpi;
System.out.println("points : "+points);
Map m = new HashMap();
m.put(TextAttribute.FAMILY, a[0]);
m.put(TextAttribute.SIZE, points);
Font f = Font.getFont(m);
if (f == null) {
throw new Exception("Font "+a[0]+" not found on your system");
}
Graphics2D g = bi.createGraphics();
FontMetrics fm = g.getFontMetrics(f);
int w = fm.charsWidth(text.toCharArray(), 0, text.length());
bi = new BufferedImage(w, height, BufferedImage.TYPE_INT_ARGB);
g = bi.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, height);
g.setColor(Color.WHITE);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g.setFont(f);
g.drawString(text, 0, fm.getMaxAscent());
String fName = a[3];
String ext = fName.substring(fName.lastIndexOf('.')+1).toLowerCase();
File file = new File(fName);
ImageWriter iw = ImageIO.getImageWritersBySuffix(ext).next();
ImageOutputStream ios = ImageIO.createImageOutputStream(file);
iw.setOutput(ios);
iw.write(bi);
ios.flush();
ios.close();
}
}
Comparing points to pixels is like kg to Newton where the acceleration may give varying conversions. AWT lets you elect a device (screen, printer), but in your case you definitely have to determine your ratio.
You may of course use Photoshop or Gimp and create a normative image for java.
After elaborated question:
Ah, I think I see the misunderstanding. An image does only concern pixels, never points, mm, DPI, or whatever. (Sometimes only as metainfo added separately to the image.)
So if you know the DPI of your device, the inches you want to use, then the dots/pixels are clear. points/dpi may shed more light.