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 hintMap = makeRenderingHints(); private static Map makeRenderingHints() { Map map = new HashMap(); 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 JComponent /* 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); //java.lang.Thread.dumpStack(); if (useWidth) { long ht = Math.round(scaledHeight); int deltaY = (int) ((displayHeight - ht) / 2); g2.drawImage(image, 0, deltaY, scaledWidth, scaledHeight, this); //System.out.println("g2.drawImage(image="+image+", 0, deltaY="+deltaY+", scaledWidth="+scaledWidth+", scaledHeight="+scaledHeight+", "+this+")"); } else { long wd = Math.round(scaledWidth); int deltaX = (int) ((displayWidth - wd) / 2); g2.drawImage(image, deltaX, 0, scaledWidth, scaledHeight, this); //System.out.println("g2.drawImage(image="+image+", deltaX="+deltaX+", 0, scaledWidth="+scaledWidth+", scaledHeight="+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; } } }