-
Bug
-
Resolution: Unresolved
-
P3
-
7, 7u15
-
os_x
FULL PRODUCT VERSION :
java version " 1.7.0_15 "
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin slytherin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64
(Mac OSX 10.7.5)
EXTRA RELEVANT SYSTEM CONFIGURATION :
Mac Pro 2.66 GHz Quad-Core Intel Xeon with 3 GB 1066 Mhz DDR3 memory. (I don't know if this is relevant.)
A DESCRIPTION OF THE PROBLEM :
Rendering an image with RenderingHints.KEY_INTERPOLATION set to
VALUE_INTERPOLATION_BICUBIC or VALUE_INTERPOLATION_BILINEAR causes
rendering to run very slowly, and renders twice when using
VALUE_INTERPOLATION_BICUBIC.
REGRESSION. Last worked in version 6u45
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
To maximize the effect, run this on the largest screen you can feasibly
use. Run the enclosed test case, which displays a test image in a
window. The repaint button lets you repaint the test image, and the
three radio buttons let you choose which type of interpolation to use
when repainting. Hit the repaint button to repaint using the default
nearest-neighbor algorithm. Then choose the Bi-linear interpolation
and hit repaint again. Observe the repainting speed. (The quality of
the black circle improves when you switch to Bi-linear painting, which
is how you can tell the painting is complete.) Then try this again
after choosing BiCubic interpolation.
Do all of this under JRE 1.6 and 1.7, and compare the results
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
For all three interpolation modes, painting of the image should be fast, and should not flicker.
ACTUAL -
I ran this on an Apple Cinema display, with the resolution set to 1920 x 1200.
Under JRE 1.6, I get the expected result. All three modes paint nearly
instantaneously. Under JRE 1.7, both Bi-linear and Bi-cubic paint
slowly. Bi-linear takes about 3 seconds, and Bi-cubic paints the image
twice, and takes about 5 seconds for both paints.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.RGBImageFilter;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
@SuppressWarnings({ " HardCodedStringLiteral " , " MagicNumber " })
public class BicubicFlickerBug6 extends JPanel {
private static final int RADIUS = 300;
private static final int DIAMETER = RADIUS * 2;
private static final int MARGIN = 10;
private static final int arcCount = 36;
private static final int radiiCount = 8;
private static final Shape outerCircle = new Arc2D.Double(0, 0, DIAMETER, DIAMETER, 0.0, 360.0, Arc2D.OPEN);
private static final Arc2D colorArc =
new Arc2D.Double(-RADIUS, -RADIUS, DIAMETER, DIAMETER, 0.0, -360.0 / arcCount, Arc2D.PIE);
private final ScaledImage scalingImage = new ScaledImage(null);
private final JComponent photoView;
private final Image loadedImage = makeTestImage();
private Image makeTestImage() {
int size = DIAMETER + (2 * MARGIN);
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) image.getGraphics();
setRenderHints(g2);
final Stroke s2 = new BasicStroke(2.0f);
g2.setStroke(s2);
AffineTransform savedTransform = g2.getTransform();
// move to the center
g2.setTransform(savedTransform);
for (int saturation = 0; saturation < radiiCount; ++saturation) {
g2.translate(MARGIN + RADIUS, MARGIN + RADIUS);
float sat = (radiiCount - saturation) / (float) radiiCount;
g2.scale(sat, sat);
g2.rotate(-Math.PI / arcCount);
for (int ii = 0; ii < arcCount; ++ii) {
g2.setColor(Color.getHSBColor((ii / (float) arcCount), sat, 1.0f));
g2.fill(colorArc);
g2.rotate((Math.PI * 2) / arcCount);
}
g2.setTransform(savedTransform);
}
g2.translate(MARGIN, MARGIN);
g2.setColor(Color.black);
g2.draw(outerCircle);
g2.setTransform(savedTransform);
g2.dispose();
return image;
}
private static final Map<RenderingHints.Key, Object> hintMap = makeRenderingHints();
private static Map<RenderingHints.Key, Object> makeRenderingHints() {
Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
map.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
return map;
}
public static void main(String[] args) {
makeFrame();
}
private static void makeFrame() {
final JFrame mainFrame = new JFrame( " BicubicFlickerBug " );
mainFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
mainFrame.add(new BicubicFlickerBug6(), BorderLayout.CENTER);
mainFrame.setExtendedState(Frame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
}
public BicubicFlickerBug6() {
super(new BorderLayout());
JToolBar toolBar = new JToolBar();
toolBar.setOrientation(SwingConstants.VERTICAL);
add(toolBar, BorderLayout.WEST);
toolBar.setFloatable(false);
JButton repaintButton = new JButton( " RePaint " );
toolBar.add(repaintButton);
repaintButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doRepaint();
}
});
ButtonGroup bGroup = new ButtonGroup();
JRadioButton biCubicButton = new JRadioButton( " BiCubic " );
bGroup.add(biCubicButton);
JRadioButton biLinearButton = new JRadioButton( " BiLinear " );
bGroup.add(biLinearButton);
JRadioButton nearestNeighborButton = new JRadioButton( " Nearest Neighbor " );
bGroup.add(nearestNeighborButton);
nearestNeighborButton.setSelected(true);
biCubicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
});
biLinearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
});
nearestNeighborButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
});
toolBar.add(nearestNeighborButton);
toolBar.add(biLinearButton);
toolBar.add(biCubicButton);
photoView = createPhotoView();
add(photoView, BorderLayout.CENTER);
}
private void doRepaint() {
//photoView.removeAll();
Image filteredImage = doFilter();
scalingImage.setImage(filteredImage);
//photoView.add(scalingImage);
scalingImage.repaint();
}
private JComponent createPhotoView() {
scalingImage.setImage(loadedImage);
JPanel photoView = new JPanel(new GridLayout(0, 1));
photoView.add(scalingImage);
return photoView;
}
private class ScaledImage extends Canvas {
private Image image;
public ScaledImage(Image image) {
this.image = image;
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
BicubicFlickerBug6.this.revalidate();
}
});
}
public void setImage(Image image) {
this.image = image;
repaint();
}
@Override
public void paint(final Graphics g) {
if (image == null) { return; }
int displayWidth = getWidth();
int imageWidth = image.getWidth(this);
double widthScale = ((double) displayWidth) / imageWidth;
int displayHeight = getHeight();
int imageHeight = image.getHeight(this);
double heightScale = ((double) displayHeight) / imageHeight;
boolean useWidth;
double scale;
if (widthScale < heightScale) {
useWidth = true;
scale = widthScale;
} else {
useWidth = false;
scale = heightScale;
}
int scaledWidth = (int) Math.round(scale * imageWidth);
int scaledHeight = (int) Math.round(scale * imageHeight);
Graphics2D g2 = (Graphics2D) g;
setRenderHints(g2);
if (useWidth) {
long ht = Math.round(scaledHeight);
int deltaY = (int) ((displayHeight - ht) / 2);
g2.drawImage(image, 0, deltaY, scaledWidth, scaledHeight, this);
} else {
long wd = Math.round(scaledWidth);
int deltaX = (int) ((displayWidth - wd) / 2);
g2.drawImage(image, deltaX, 0, scaledWidth, scaledHeight, this);
}
}
}
private Image doFilter() {
ImageFilter filter = new NullFilter();
FilteredImageSource imageSource = new FilteredImageSource(loadedImage.getSource(), filter);
return photoView.createImage(imageSource);
}
private static void setRenderHints(Graphics2D g2) {
g2.setRenderingHints(hintMap);
}
private static class NullFilter extends RGBImageFilter {
public NullFilter() { }
@Override
public int filterRGB(final int x, final int y, final int rgb) {
return rgb;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The only workaround is to use JRE 1.6
java version " 1.7.0_15 "
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin slytherin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64
(Mac OSX 10.7.5)
EXTRA RELEVANT SYSTEM CONFIGURATION :
Mac Pro 2.66 GHz Quad-Core Intel Xeon with 3 GB 1066 Mhz DDR3 memory. (I don't know if this is relevant.)
A DESCRIPTION OF THE PROBLEM :
Rendering an image with RenderingHints.KEY_INTERPOLATION set to
VALUE_INTERPOLATION_BICUBIC or VALUE_INTERPOLATION_BILINEAR causes
rendering to run very slowly, and renders twice when using
VALUE_INTERPOLATION_BICUBIC.
REGRESSION. Last worked in version 6u45
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
To maximize the effect, run this on the largest screen you can feasibly
use. Run the enclosed test case, which displays a test image in a
window. The repaint button lets you repaint the test image, and the
three radio buttons let you choose which type of interpolation to use
when repainting. Hit the repaint button to repaint using the default
nearest-neighbor algorithm. Then choose the Bi-linear interpolation
and hit repaint again. Observe the repainting speed. (The quality of
the black circle improves when you switch to Bi-linear painting, which
is how you can tell the painting is complete.) Then try this again
after choosing BiCubic interpolation.
Do all of this under JRE 1.6 and 1.7, and compare the results
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
For all three interpolation modes, painting of the image should be fast, and should not flicker.
ACTUAL -
I ran this on an Apple Cinema display, with the resolution set to 1920 x 1200.
Under JRE 1.6, I get the expected result. All three modes paint nearly
instantaneously. Under JRE 1.7, both Bi-linear and Bi-cubic paint
slowly. Bi-linear takes about 3 seconds, and Bi-cubic paints the image
twice, and takes about 5 seconds for both paints.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.RGBImageFilter;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
@SuppressWarnings({ " HardCodedStringLiteral " , " MagicNumber " })
public class BicubicFlickerBug6 extends JPanel {
private static final int RADIUS = 300;
private static final int DIAMETER = RADIUS * 2;
private static final int MARGIN = 10;
private static final int arcCount = 36;
private static final int radiiCount = 8;
private static final Shape outerCircle = new Arc2D.Double(0, 0, DIAMETER, DIAMETER, 0.0, 360.0, Arc2D.OPEN);
private static final Arc2D colorArc =
new Arc2D.Double(-RADIUS, -RADIUS, DIAMETER, DIAMETER, 0.0, -360.0 / arcCount, Arc2D.PIE);
private final ScaledImage scalingImage = new ScaledImage(null);
private final JComponent photoView;
private final Image loadedImage = makeTestImage();
private Image makeTestImage() {
int size = DIAMETER + (2 * MARGIN);
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) image.getGraphics();
setRenderHints(g2);
final Stroke s2 = new BasicStroke(2.0f);
g2.setStroke(s2);
AffineTransform savedTransform = g2.getTransform();
// move to the center
g2.setTransform(savedTransform);
for (int saturation = 0; saturation < radiiCount; ++saturation) {
g2.translate(MARGIN + RADIUS, MARGIN + RADIUS);
float sat = (radiiCount - saturation) / (float) radiiCount;
g2.scale(sat, sat);
g2.rotate(-Math.PI / arcCount);
for (int ii = 0; ii < arcCount; ++ii) {
g2.setColor(Color.getHSBColor((ii / (float) arcCount), sat, 1.0f));
g2.fill(colorArc);
g2.rotate((Math.PI * 2) / arcCount);
}
g2.setTransform(savedTransform);
}
g2.translate(MARGIN, MARGIN);
g2.setColor(Color.black);
g2.draw(outerCircle);
g2.setTransform(savedTransform);
g2.dispose();
return image;
}
private static final Map<RenderingHints.Key, Object> hintMap = makeRenderingHints();
private static Map<RenderingHints.Key, Object> makeRenderingHints() {
Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
map.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
return map;
}
public static void main(String[] args) {
makeFrame();
}
private static void makeFrame() {
final JFrame mainFrame = new JFrame( " BicubicFlickerBug " );
mainFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
mainFrame.add(new BicubicFlickerBug6(), BorderLayout.CENTER);
mainFrame.setExtendedState(Frame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
}
public BicubicFlickerBug6() {
super(new BorderLayout());
JToolBar toolBar = new JToolBar();
toolBar.setOrientation(SwingConstants.VERTICAL);
add(toolBar, BorderLayout.WEST);
toolBar.setFloatable(false);
JButton repaintButton = new JButton( " RePaint " );
toolBar.add(repaintButton);
repaintButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doRepaint();
}
});
ButtonGroup bGroup = new ButtonGroup();
JRadioButton biCubicButton = new JRadioButton( " BiCubic " );
bGroup.add(biCubicButton);
JRadioButton biLinearButton = new JRadioButton( " BiLinear " );
bGroup.add(biLinearButton);
JRadioButton nearestNeighborButton = new JRadioButton( " Nearest Neighbor " );
bGroup.add(nearestNeighborButton);
nearestNeighborButton.setSelected(true);
biCubicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
});
biLinearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
});
nearestNeighborButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
});
toolBar.add(nearestNeighborButton);
toolBar.add(biLinearButton);
toolBar.add(biCubicButton);
photoView = createPhotoView();
add(photoView, BorderLayout.CENTER);
}
private void doRepaint() {
//photoView.removeAll();
Image filteredImage = doFilter();
scalingImage.setImage(filteredImage);
//photoView.add(scalingImage);
scalingImage.repaint();
}
private JComponent createPhotoView() {
scalingImage.setImage(loadedImage);
JPanel photoView = new JPanel(new GridLayout(0, 1));
photoView.add(scalingImage);
return photoView;
}
private class ScaledImage extends Canvas {
private Image image;
public ScaledImage(Image image) {
this.image = image;
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
BicubicFlickerBug6.this.revalidate();
}
});
}
public void setImage(Image image) {
this.image = image;
repaint();
}
@Override
public void paint(final Graphics g) {
if (image == null) { return; }
int displayWidth = getWidth();
int imageWidth = image.getWidth(this);
double widthScale = ((double) displayWidth) / imageWidth;
int displayHeight = getHeight();
int imageHeight = image.getHeight(this);
double heightScale = ((double) displayHeight) / imageHeight;
boolean useWidth;
double scale;
if (widthScale < heightScale) {
useWidth = true;
scale = widthScale;
} else {
useWidth = false;
scale = heightScale;
}
int scaledWidth = (int) Math.round(scale * imageWidth);
int scaledHeight = (int) Math.round(scale * imageHeight);
Graphics2D g2 = (Graphics2D) g;
setRenderHints(g2);
if (useWidth) {
long ht = Math.round(scaledHeight);
int deltaY = (int) ((displayHeight - ht) / 2);
g2.drawImage(image, 0, deltaY, scaledWidth, scaledHeight, this);
} else {
long wd = Math.round(scaledWidth);
int deltaX = (int) ((displayWidth - wd) / 2);
g2.drawImage(image, deltaX, 0, scaledWidth, scaledHeight, this);
}
}
}
private Image doFilter() {
ImageFilter filter = new NullFilter();
FilteredImageSource imageSource = new FilteredImageSource(loadedImage.getSource(), filter);
return photoView.createImage(imageSource);
}
private static void setRenderHints(Graphics2D g2) {
g2.setRenderingHints(hintMap);
}
private static class NullFilter extends RGBImageFilter {
public NullFilter() { }
@Override
public int filterRGB(final int x, final int y, final int rgb) {
return rgb;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The only workaround is to use JRE 1.6
- relates to
-
JDK-8059942 Default implementation of DrawImage.renderImageXform() should be improved for d3d/ogl
-
- Resolved
-