1

I'm drawing some text over an image using LineBreakMeasurer in conjunction with TextLayout but for some reason the stroke is only stroking the inside, and it's not very clean. Here's an example of what I'm talking about: https://i.stack.imgur.com/hnCQU.png

And when I don't draw the letter over top and increase the stroke width, it actually will get thicker on the inside and not outside.

Here's my code:

        float y = 0.0f;
        float wrappingWidth = img.getWidth() * 0.8f;
        LineBreakMeasurer measurer = new LineBreakMeasurer(str.getIterator(), imageGraphics.getFontRenderContext());
        while (measurer.getPosition() < sentence.length()) {
            TextLayout layout = measurer.nextLayout(wrappingWidth);
            y += layout.getAscent();
            float x =  ((wrappingWidth * 0.8f) - layout.getVisibleAdvance()) / 2f + (wrappingWidth * 0.2f);

            AffineTransform transform = new AffineTransform();
            transform.translate((double)x, (double)y);
            Shape outline = layout.getOutline(transform);

            imageGraphics.setColor(Color.black);
            imageGraphics.setClip(outline);
            imageGraphics.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
            imageGraphics.draw(outline);
            imageGraphics.setColor(Color.white);
            imageGraphics.setStroke(new BasicStroke());

            layout.draw(imageGraphics, x, y);
            y += layout.getDescent() + layout.getLeading();
        }

I'm not sure what I'm doing wrong. Does anyone know?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
blend
  • 132
  • 1
  • 9
  • 1
    Have you considered translating the `outline`? The `outline` will be painted at the same location as the text? Also, you never reset the stroke, so the text is likely been painted with the same stroke.... – MadProgrammer Mar 26 '15 at 00:44
  • Ah nice catch about the not resetting stroke bit. I was resetting it initially but wanted to see if the two would overlap perfectly if I used the same stroke. Apparently not. But yeah the result is exactly the same when I reset the stroke. I didn't realise it was necessary to translate the outline? Shouldn't the stroke widen it from both sides? – blend Mar 26 '15 at 00:47
  • I'm not too familiar with `TextLayout` but this seems kind of unusual to me: `imageGraphics.setClip(outline);` The clip might occlude the stroke (which fills outside the shape). Also this could be a `RenderingHints` issue. – Radiodef Mar 26 '15 at 00:48
  • I thought it would clip it before the stroke and so not occlude it? I removed the clipping and the stroke is actually working now. It's just really poor quality. http://i.imgur.com/ztSW7FS.png – blend Mar 26 '15 at 00:54

1 Answers1

2

Create another copy of the Graphics context before you draw the outline...

Graphics2D sg = (Graphics2D)imageGraphics.create();
sg.setColor(Color.black);
sg.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
sg.draw(outline);
sg.dispose();

I'd also get rid of the clip...

Instead of "stroking" the resulting shape, I'd be tempted to "fill" the background color and "draw" the outline color ontop of it, for example...

Outline

Graphics2D sg = (Graphics2D) g2d.create();
AffineTransform transform = new AffineTransform();
transform.translate((double) drawPosX, (double) drawPosY);
Shape outline = layout.getOutline(transform);

sg.setColor(Color.WHITE);
sg.fill(outline);
sg.setColor(Color.BLACK);
sg.draw(outline);
sg.dispose();

But if you want a "nice" thick stroke, use BasicStroke.JOIN_ROUND instead of BasicStroke.JOIN_MITER

Stroked

Graphics2D sg = (Graphics2D) g2d.create();
AffineTransform transform = new AffineTransform();
transform.translate((double) drawPosX, (double) drawPosY);
Shape outline = layout.getOutline(transform);

sg.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
sg.setColor(Color.BLACK);
sg.draw(outline);
sg.dispose();
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • That worked really well. In retrospect I have no idea why I was trying to stroke the outside of the characters. Some ideology derived from graphics editors clouding my judgement I guess :) Thanks a lot. – blend Mar 26 '15 at 01:18
  • "Use JOIN_ROUND instead of JOIN_ROUND"? – user253751 Mar 26 '15 at 01:20
  • Your last code snippet still uses JOIN_MITER. And what does using a separate Graphics2D object do? – user253751 Mar 26 '15 at 01:29
  • 1
    @immibis Ain't copy and paste wonderful. The purpose of creating a second `Graphics` context is to preserve the original context from any changes I might make (like applying a new stroke or transformation), it's much simpler to call `Graphics#dispose` then to work out how to back out a compound transformation or other changes which you might forget – MadProgrammer Mar 26 '15 at 01:32
  • Hey can you tell me what rendering hints you used? I can't seem to get it perfect like yours. – blend Mar 26 '15 at 03:04
  • 1
    The same ones from this [answer](http://stackoverflow.com/questions/16729095/assigning-a-image-to-a-string/16729305#16729305) ;) – MadProgrammer Mar 26 '15 at 03:05
  • 1
    By the way there were some cases with a few fonts (ie. impact) where those rendering hints had some slight issues. I changed the interpolation to bicubic and it's now resolved :) – blend Mar 26 '15 at 04:36