Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Java > Hair-lines when drawing transformed BufferedImage

Reply
Thread Tools

Hair-lines when drawing transformed BufferedImage

 
 
Thomas Fritsch
Guest
Posts: n/a
 
      09-06-2005
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 7FFFFE
>

imagemask
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_CLOS E);
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_ANTIA LIASING,
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[i] = (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------------------------------------------------
--
"TFritsch$t-online:de".replace(':','.').replace('$','@')


 
Reply With Quote
 
 
 
 
Roedy Green
Guest
Posts: n/a
 
      09-06-2005
On Tue, 6 Sep 2005 02:23:30 +0200, "Thomas Fritsch"
<(E-Mail Removed)> wrote or quoted :

>I'm having trouble with drawing transformed images.
>On some images there are hair-lines displayed at the top or
>left edge,


Just making some guesses here. Is flipping back and forth between
integer and float pixel co-ordinates enough to get you off by one, so
that your generated image is shifted one pixel leaving an
uninitialised hairline?

You are familiar with the co-ordinate system that fits "between" the
pixels and leads to some gotchas. You know that drawRectangle will
create a rectangle measured in pixels taller by one than the height
you requested. The model is a pen slopping over and drawing below the
co-ordinate line.

You might like to read http://mindprod.com/jgloss/coordinates.html
to review some of the gotchas you may have overlooked.

I have added a new diagram to explain the off by one gotcha.
--
Canadian Mind Products, Roedy Green.
http://mindprod.com Again taking new Java programming contracts.
 
Reply With Quote
 
 
 
 
jan V
Guest
Posts: n/a
 
      09-06-2005
Hi Thomas,

I'm getting the following bomb:

Exception in thread "main" java.lang.IllegalArgumentException: Number of
color/alpha components should be 4 but length of bits array is 3

at java.awt.image.ColorModel.<init>(Unknown Source)
at java.awt.image.IndexColorModel.<init>(Unknown Source)
at TestComponent.createMaskColorModel(TestComponent.j ava:145)
at TestComponent.imageMask(TestComponent.java:123)
at TestComponent.testImageMask(TestComponent.java:7
at TestComponent.main(TestComponent.java:17)


 
Reply With Quote
 
Thomas Fritsch
Guest
Posts: n/a
 
      09-06-2005
"jan V" <(E-Mail Removed)> wrote:
> Hi Thomas,
>
> I'm getting the following bomb:
>
> Exception in thread "main" java.lang.IllegalArgumentException: Number of
> color/alpha components should be 4 but length of bits array is 3
>
> at java.awt.image.ColorModel.<init>(Unknown Source)
> at java.awt.image.IndexColorModel.<init>(Unknown Source)
> at TestComponent.createMaskColorModel(TestComponent.j ava:145)
> at TestComponent.imageMask(TestComponent.java:123)
> at TestComponent.testImageMask(TestComponent.java:7
> at TestComponent.main(TestComponent.java:17)
>


Hi Jan!

Arrrrgh, that is hard!
Meanwhile I have pasted back my posted code, recompiled it, rerun it,
redebugged it, and got no error. When debugging up to the point in
ColorModel.java given in your stack trace
if (bits.length < numComponents) {
throw new IllegalArgumentException("Number of color/alpha "+
"components should be "+
numComponents+
" but length of bits array is "+
bits.length);
}
I got no exception. The values at that moment were bits.length=3 and
numComponents=3.
I debugged it with JDK 1.4.2_05 and 1.5.0. It is the same in both.
Can it be you used JDK 1.5.1, and Sun's ColorModel code is slightly
different there?
May be I should spend the time and download 1.5.1, too.

Anyway, Jan: Thanks for your effort!!

--
Being clueless
Thomas




 
Reply With Quote
 
Thomas Fritsch
Guest
Posts: n/a
 
      09-06-2005
"Roedy Green" wrote:
> On Tue, 6 Sep 2005 02:23:30 +0200, "Thomas Fritsch"
> <(E-Mail Removed)> wrote or quoted :
>
>>I'm having trouble with drawing transformed images.
>>On some images there are hair-lines displayed at the top or
>>left edge,

>
> Just making some guesses here. Is flipping back and forth between
> integer and float pixel co-ordinates enough to get you off by one, so
> that your generated image is shifted one pixel leaving an
> uninitialised hairline?
>
> You are familiar with the co-ordinate system that fits "between" the
> pixels and leads to some gotchas. You know that drawRectangle will
> create a rectangle measured in pixels taller by one than the height
> you requested. The model is a pen slopping over and drawing below the
> co-ordinate line.


I've read a bit about this, but certainly not enough as I feel now.
But: The only place where I draw a rectangle is in my erasePage (a white
fillRect for the whole large page), which I think is not the critical part.
I thought the critical part should be somewhere near drawImage. I guess your
reasoning applies to drawImage as well.
>
> You might like to read http://mindprod.com/jgloss/coordinates.html
> to review some of the gotchas you may have overlooked.

Thanks, I will read this.
>
> I have added a new diagram to explain the off by one gotcha.


--
"TFritsch$t-online:de".replace(':','.').replace('$','@')


 
Reply With Quote
 
Chris Uppal
Guest
Posts: n/a
 
      09-06-2005
Thomas Fritsch wrote:

> 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.


I think that it's a side effect of rounding. More specifically, I /suspect/
that it might be a screw-up somewhere in Sun's code where one bitis /rounding/
floating point co-ordinates, and another part is /truncating/ them.

(Oh, BTW, your example ran fine for me with 1.5.0 b64 running on WinXP)

Anyway. If I set your screenResolution field to 72 (so that the transforma
have nice round numbers), then the display works fine. In that case the
transform that we end up with when we draw your graphic takes the unit square
(0,0) -> (1,1) onto the pixel co-ordinates:

(50.0, 192.0) -> (170.0, 72.0)

If I change screenResolution to 73, then the image shows hair-lines at both the
left and right edges, and in that case the transfomation is taking the unit
square to:

(50.69, 194.67) -> (172.36, 73.00)

Looking closely at the image, the "legitimate" pixels have been drawn with X
values in the range 51 to 171 inclusive, the hairlines are at X values 50 and
172. Taking that fact in tandem with the observation that the hairlines do not
extend the full height of the rectangle, and that there is no hairlines at the
bottom of the rectange, I suspect that it's a bug in the rendering code, and
that it has to do with rounding vs. truncation of X values.

As to how to fix it, I have no clever suggestions Maybe there is some easy
way, but from where I sit it looks as if you may have to fiddle with the
transformation in imageMask() to ensure that it produces a transform that maps
to whole numbers of pixels.

-- chris


 
Reply With Quote
 
Roedy Green
Guest
Posts: n/a
 
      09-06-2005
On Tue, 6 Sep 2005 11:45:42 +0200, "Thomas Fritsch"
<(E-Mail Removed)> wrote or quoted :

>I've read a bit about this, but certainly not enough as I feel now.
>But: The only place where I draw a rectangle is in my erasePage (a white
>fillRect for the whole large page), which I think is not the critical part.
>I thought the critical part should be somewhere near drawImage. I guess your
>reasoning applies to drawImage as well.


I would need to experiment, but perhaps this oddity pervades
everything, including Images.

--
Canadian Mind Products, Roedy Green.
http://mindprod.com Again taking new Java programming contracts.
 
Reply With Quote
 
John B. Matthews
Guest
Posts: n/a
 
      09-06-2005
In article <431d6f08$0$38038$(E-Mail Removed)>,
"Chris Uppal" <(E-Mail Removed)-THIS.org> wrote:

> Thomas Fritsch wrote:
>
> > 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.

>
> I think that it's a side effect of rounding. More specifically, I
> /suspect/ that it might be a screw-up somewhere in Sun's code where
> one bit is /rounding/ floating point co-ordinates, and another part
> is /truncating/ them.
>
> (Oh, BTW, your example ran fine for me with 1.5.0 b64 running on WinXP)
>
> Anyway. If I set your screenResolution field to 72 (so that the
> transforma have nice round numbers), then the display works fine. In
> that case the transform that we end up with when we draw your graphic
> takes the unit square (0,0) -> (1,1) onto the pixel co-ordinates:
>
> (50.0, 192.0) -> (170.0, 72.0)

[...]

I am unable to reproduce this effect with any of several values of
screenResolution, using java 1.4.2_07 on Mac OS X 10.4.2. I don't know
where Sun's code leaves off and Microsoft's begins, but could this be a
problem with the underlying OS rendering code?

--
John
jmatthews at wright dot edu
www dot wright dot edu/~john.matthews/
 
Reply With Quote
 
Thomas Fritsch
Guest
Posts: n/a
 
      09-06-2005
Thomas Fritsch wrote:
> I got no exception. The values at that moment were bits.length=3 and
> numComponents=3.
> I debugged it with JDK 1.4.2_05 and 1.5.0. It is the same in both.

... with 1.5.0_04, as I should have written more precisely.
> Can it be you used JDK 1.5.1, and Sun's ColorModel code is slightly
> different there?
> May be I should spend the time and download 1.5.1, too.

My assumption was nonsense. There is no 1.5.1 at java.sun.com, the
newest I found is 1.5.0_04 (aka "JDK 5.0 Update 4"), and "J2SDK 6.0".
>

--
Thomas

 
Reply With Quote
 
Chris Uppal
Guest
Posts: n/a
 
      09-06-2005
John B. Matthews wrote:

> I am unable to reproduce this effect with any of several values of
> screenResolution, using java 1.4.2_07 on Mac OS X 10.4.2. I don't know
> where Sun's code leaves off and Microsoft's begins, but could this be a
> problem with the underlying OS rendering code?


Presumably, yes. I don't see how it could be in the Windows code itself (the
Windows graphics API doesn't understand floating point at all) but in the
OS-specific parts of the Java implementation.

But it must be in a fairly generic part of the OS-specific code (if that's the
problem at all, of course since I can reproduce the problem using 1.4.2 on
Linux as well as 1.5.0 on Windows.

-- chris


 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Guide to the standard library? Library for drawing in GUIs?drawing inbrowsers? defn noob Java 1 06-28-2008 02:50 AM
funny drawing software:ScreenPen,drawing directly on screen! yyzzbb@sina.com Digital Photography 0 02-04-2006 12:31 AM
System.Drawing For Drawing Text Images jjbutera@hotmail.com ASP .Net 1 01-09-2006 09:55 PM
Re: Hair-lines when drawing transformed BufferedImage Thomas Fritsch Java 3 12-06-2005 07:23 PM
Non-english text getting transformed. AC ASP .Net 8 02-26-2004 02:13 PM



Advertisments