T
Thomas Fritsch
Hello all,
I'm having trouble with drawing transformed images.
On some images there are hair-lines displayed at the top or
left edge, which are not part of the original image data,
but seem to be an artefact of some process under the hood.
I don't understand why and when such hair-lines occur.
How can I avoid them? Have they something to do with
AffineTransform? I hope somebody here can help me out.
At the end is the unavoidable SSCCE for reproducing the problem.
Sorry, but it is still a bit lengthy (150 lines).
I have reproduced the problem using Java 1.4.2 + 1.5.0, WinXP.
The example shows an X-shaped 24x24 image consisting of black
and transparent pixels on a white background. The problem is
the hair-line hanging down the top-left corner of the X. There
are quite many AffineTransforms involved in processing, finally
scaling up each pixel of the "X" to 6.66x6.66 dots on screen.
Because the problem might depend on the screen resolution, I
didn't use Toolkit#getScreenResolution in the SSCCE here, but
hard-coded 96 (the resolution of my screen) instead.
Side note to those of you, familiar with the PostScript
language. The Java code at the end tries to mimic this PS code:
%!PS
50 600 translate
120 120 scale
24 24 false [24 0 0 -24 0 24]
<7FFFFE BFFFFD DFFFFB EFFFF7 F7FFEF FBFFDF FDFFBF FEFF7F
FF7EFF FFBDFF FFDBFF FFE7FF FFE7FF FFDBFF FFBDFF FF7EFF
FEFF7F FDFFBF FBFFDF F7FFEF EFFFF7 DFFFFB BFFFFD 7FFFFEimagemask
Have this in mind when reading the Java code.
Thanks in advance
Thomas
//--Begin SSCCE-------------------------------------------------
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.*;
import java.io.*;
import javax.swing.*;
public class TestComponent extends JComponent {
public static void main(String args[]) throws Exception {
TestComponent comp = new TestComponent();
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(comp));
frame.pack();
frame.setVisible(true);
comp.testImageMask();
}
private int screenResolution = 96;
// instead of getToolkit().getScreenResolution();
private BufferedImage pageImage;
private Graphics2D graphics;
TestComponent() {
// Creates an image [8.5 x 11 inch]
int w = (int) (8.5 * screenResolution);
int h = 11 * screenResolution;
pageImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
}
public Dimension getPreferredSize() {
int w = pageImage.getWidth();
int h = pageImage.getHeight();
return new Dimension(w, h);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.drawImage(pageImage, 0, 0, this);
g2.dispose();
}
// data for 24x24 image, 1 bit per pixel
private byte pixelData[] = {
(byte)0x7F, (byte)0xFF, (byte)0xFE,
(byte)0xBF, (byte)0xFF, (byte)0xFD,
(byte)0xDF, (byte)0xFF, (byte)0xFB,
(byte)0xEF, (byte)0xFF, (byte)0xF7,
(byte)0xF7, (byte)0xFF, (byte)0xEF,
(byte)0xFB, (byte)0xFF, (byte)0xDF,
(byte)0xFD, (byte)0xFF, (byte)0xBF,
(byte)0xFE, (byte)0xFF, (byte)0x7F,
(byte)0xFF, (byte)0x7E, (byte)0xFF,
(byte)0xFF, (byte)0xBD, (byte)0xFF,
(byte)0xFF, (byte)0xDB, (byte)0xFF,
(byte)0xFF, (byte)0xE7, (byte)0xFF,
(byte)0xFF, (byte)0xE7, (byte)0xFF,
(byte)0xFF, (byte)0xDB, (byte)0xFF,
(byte)0xFF, (byte)0xBD, (byte)0xFF,
(byte)0xFF, (byte)0x7E, (byte)0xFF,
(byte)0xFE, (byte)0xFF, (byte)0x7F,
(byte)0xFD, (byte)0xFF, (byte)0xBF,
(byte)0xFB, (byte)0xFF, (byte)0xDF,
(byte)0xF7, (byte)0xFF, (byte)0xEF,
(byte)0xEF, (byte)0xFF, (byte)0xF7,
(byte)0xDF, (byte)0xFF, (byte)0xFB,
(byte)0xBF, (byte)0xFF, (byte)0xFD,
(byte)0x7F, (byte)0xFF, (byte)0xFE
};
void testImageMask() throws Exception {
erasePage();
initGraphics();
translate(50, 600);
scale(120, 120);
imageMask(24, 24, false,
new AffineTransform(24, 0, 0, -24, 0, 24),
new ByteArrayInputStream(pixelData));
}
// Fills the page image completely with white
void erasePage() {
Graphics2D g = pageImage.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, pageImage.getWidth(), pageImage.getHeight());
g.dispose();
repaint();
}
void initGraphics() {
graphics = pageImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setTransform(defaultMatrix());
graphics.setColor(Color.black);
}
// Gets a transformation transforming from [origin in top-left,
// screen resolution] to [origin in bottom-left, 72 dpi]
AffineTransform defaultMatrix() {
AffineTransform at = new AffineTransform();
at.translate(0, pageImage.getHeight());
at.scale(screenResolution / 72.0, - screenResolution / 72.0);
return at;
}
void translate(float x, float y) {
graphics.translate(x, y);
}
void scale(float sx, float sy) {
graphics.scale(sx, sy);
}
// Reads a byte-stream of pixel-data, interprets them as colored
// or transparent pixels, and draws them onto the page image
void imageMask(int width, int height, boolean coloredIs1,
AffineTransform transform, InputStream stream)
throws IOException, NoninvertibleTransformException {
// Prepare an image with the same byte-layout as the stream
ColorModel cm = createMaskColorModel(coloredIs1);
WritableRaster raster =
cm.createCompatibleWritableRaster(width, height);
byte b[] = ((DataBufferByte) raster.getDataBuffer()).getData();
// Copy the stream straight into the image's data buffer.
for (int i = 0; i < b.length; i++) {
int c = stream.read();
if (c == -1)
break;
b = (byte) c;
}
BufferedImage image = new BufferedImage(cm, raster, false, null);
graphics.drawImage(image, transform.createInverse(), null);
repaint();
}
// Creates a 1 bit/pixel color model. Its 2 colors are:
// the current graphics color; a total transparent color.
private ColorModel createMaskColorModel(boolean coloredIs1) {
int rgb = graphics.getColor().getRGB();
int cmap[] = { rgb, rgb };
int trans = coloredIs1 ? 0 : 1;
return new IndexColorModel(1, 2, cmap, 0, false, trans,
DataBuffer.TYPE_BYTE);
}
}
//--End SSCCE------------------------------------------------
I'm having trouble with drawing transformed images.
On some images there are hair-lines displayed at the top or
left edge, which are not part of the original image data,
but seem to be an artefact of some process under the hood.
I don't understand why and when such hair-lines occur.
How can I avoid them? Have they something to do with
AffineTransform? I hope somebody here can help me out.
At the end is the unavoidable SSCCE for reproducing the problem.
Sorry, but it is still a bit lengthy (150 lines).
I have reproduced the problem using Java 1.4.2 + 1.5.0, WinXP.
The example shows an X-shaped 24x24 image consisting of black
and transparent pixels on a white background. The problem is
the hair-line hanging down the top-left corner of the X. There
are quite many AffineTransforms involved in processing, finally
scaling up each pixel of the "X" to 6.66x6.66 dots on screen.
Because the problem might depend on the screen resolution, I
didn't use Toolkit#getScreenResolution in the SSCCE here, but
hard-coded 96 (the resolution of my screen) instead.
Side note to those of you, familiar with the PostScript
language. The Java code at the end tries to mimic this PS code:
%!PS
50 600 translate
120 120 scale
24 24 false [24 0 0 -24 0 24]
<7FFFFE BFFFFD DFFFFB EFFFF7 F7FFEF FBFFDF FDFFBF FEFF7F
FF7EFF FFBDFF FFDBFF FFE7FF FFE7FF FFDBFF FFBDFF FF7EFF
FEFF7F FDFFBF FBFFDF F7FFEF EFFFF7 DFFFFB BFFFFD 7FFFFEimagemask
Have this in mind when reading the Java code.
Thanks in advance
Thomas
//--Begin SSCCE-------------------------------------------------
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.*;
import java.io.*;
import javax.swing.*;
public class TestComponent extends JComponent {
public static void main(String args[]) throws Exception {
TestComponent comp = new TestComponent();
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(comp));
frame.pack();
frame.setVisible(true);
comp.testImageMask();
}
private int screenResolution = 96;
// instead of getToolkit().getScreenResolution();
private BufferedImage pageImage;
private Graphics2D graphics;
TestComponent() {
// Creates an image [8.5 x 11 inch]
int w = (int) (8.5 * screenResolution);
int h = 11 * screenResolution;
pageImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
}
public Dimension getPreferredSize() {
int w = pageImage.getWidth();
int h = pageImage.getHeight();
return new Dimension(w, h);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.drawImage(pageImage, 0, 0, this);
g2.dispose();
}
// data for 24x24 image, 1 bit per pixel
private byte pixelData[] = {
(byte)0x7F, (byte)0xFF, (byte)0xFE,
(byte)0xBF, (byte)0xFF, (byte)0xFD,
(byte)0xDF, (byte)0xFF, (byte)0xFB,
(byte)0xEF, (byte)0xFF, (byte)0xF7,
(byte)0xF7, (byte)0xFF, (byte)0xEF,
(byte)0xFB, (byte)0xFF, (byte)0xDF,
(byte)0xFD, (byte)0xFF, (byte)0xBF,
(byte)0xFE, (byte)0xFF, (byte)0x7F,
(byte)0xFF, (byte)0x7E, (byte)0xFF,
(byte)0xFF, (byte)0xBD, (byte)0xFF,
(byte)0xFF, (byte)0xDB, (byte)0xFF,
(byte)0xFF, (byte)0xE7, (byte)0xFF,
(byte)0xFF, (byte)0xE7, (byte)0xFF,
(byte)0xFF, (byte)0xDB, (byte)0xFF,
(byte)0xFF, (byte)0xBD, (byte)0xFF,
(byte)0xFF, (byte)0x7E, (byte)0xFF,
(byte)0xFE, (byte)0xFF, (byte)0x7F,
(byte)0xFD, (byte)0xFF, (byte)0xBF,
(byte)0xFB, (byte)0xFF, (byte)0xDF,
(byte)0xF7, (byte)0xFF, (byte)0xEF,
(byte)0xEF, (byte)0xFF, (byte)0xF7,
(byte)0xDF, (byte)0xFF, (byte)0xFB,
(byte)0xBF, (byte)0xFF, (byte)0xFD,
(byte)0x7F, (byte)0xFF, (byte)0xFE
};
void testImageMask() throws Exception {
erasePage();
initGraphics();
translate(50, 600);
scale(120, 120);
imageMask(24, 24, false,
new AffineTransform(24, 0, 0, -24, 0, 24),
new ByteArrayInputStream(pixelData));
}
// Fills the page image completely with white
void erasePage() {
Graphics2D g = pageImage.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, pageImage.getWidth(), pageImage.getHeight());
g.dispose();
repaint();
}
void initGraphics() {
graphics = pageImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setTransform(defaultMatrix());
graphics.setColor(Color.black);
}
// Gets a transformation transforming from [origin in top-left,
// screen resolution] to [origin in bottom-left, 72 dpi]
AffineTransform defaultMatrix() {
AffineTransform at = new AffineTransform();
at.translate(0, pageImage.getHeight());
at.scale(screenResolution / 72.0, - screenResolution / 72.0);
return at;
}
void translate(float x, float y) {
graphics.translate(x, y);
}
void scale(float sx, float sy) {
graphics.scale(sx, sy);
}
// Reads a byte-stream of pixel-data, interprets them as colored
// or transparent pixels, and draws them onto the page image
void imageMask(int width, int height, boolean coloredIs1,
AffineTransform transform, InputStream stream)
throws IOException, NoninvertibleTransformException {
// Prepare an image with the same byte-layout as the stream
ColorModel cm = createMaskColorModel(coloredIs1);
WritableRaster raster =
cm.createCompatibleWritableRaster(width, height);
byte b[] = ((DataBufferByte) raster.getDataBuffer()).getData();
// Copy the stream straight into the image's data buffer.
for (int i = 0; i < b.length; i++) {
int c = stream.read();
if (c == -1)
break;
b = (byte) c;
}
BufferedImage image = new BufferedImage(cm, raster, false, null);
graphics.drawImage(image, transform.createInverse(), null);
repaint();
}
// Creates a 1 bit/pixel color model. Its 2 colors are:
// the current graphics color; a total transparent color.
private ColorModel createMaskColorModel(boolean coloredIs1) {
int rgb = graphics.getColor().getRGB();
int cmap[] = { rgb, rgb };
int trans = coloredIs1 ? 0 : 1;
return new IndexColorModel(1, 2, cmap, 0, false, trans,
DataBuffer.TYPE_BYTE);
}
}
//--End SSCCE------------------------------------------------