Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clipToRect not correctly applied to fillGlyphRun within rotated context #193

Open
squrky opened this issue Feb 28, 2024 · 4 comments
Open
Labels

Comments

@squrky
Copy link

squrky commented Feb 28, 2024

Hi,

Glyph rendering doesn't seem to honour clipping when the glyph and clip-rect are rotated. What follows is a fairly indepth demo of precise control over glyph positioning. The objective here is to place a glyph at the centre of the image with very specific scaling, rotate and clip it. See the four images at the bottom which show the behaviour with and without rotation and clipping.

Many of the steps below are not required to trigger this bug (assuming that is what this is), but because of the complexities of positioning glyphs accurately/correctly I felt it necessary to set out a full demo.

Set up a font and size it for precise control over ascent+descent height:

// Load a font face
BLFontFace face;
face.createFromFile("c:\\Windows\\Fonts\\DejaVuSansMono.ttf");

// Make a font and eliminate initial pixel scaling
BLFont font;
font.createFromFace(face, 1.0f);

// Get scalar multiplier that can be used to map font ascent+descent to pixels accurately
const auto font_scale = [&]()
{
  const auto fm = font.metrics();
  return 1.0 / (fm.ascent + fm.descent);
}();

// Set font height such that ascent+descent is exactly `design_height` pixels
// All design units that follow are in pixels
const int design_height = 150;
font.setSize(design_height * font_scale);

// Get font baseline for positioning later
const int design_ascent = [&]()
{
  const auto fm = font.metrics();
  return static_cast<int>(fm.ascent);
}();

// Get font design grid width
const auto design_width = [&]()
{
  BLGlyphBuffer gb;
  gb.setUtf8Text(""); // We can use any glyph that spans the entire design grid for this
  font.shape(gb);    

  BLTextMetrics tm;
  font.getTextMetrics(gb, tm);
  return tm.advance.x; 
}();

// Font design grid in pixels
const BLRect design_grid{0, 0, design_width, design_height};

Create the output image and bind the context, set the origin to the centre of the image:

// Make test image and context
const auto test_width = 200;
const auto test_height = 200;
BLImage img(test_width, test_height, BL_FORMAT_PRGB32);
BLContext ctx(img);
ctx.clearAll();

// Draw image boundary
ctx.setStrokeStyle(BLRgba32{255, 255, 0, 255});
ctx.strokeRect(BLRect{0, 0, test_width, test_height});

// Move the origin to the centre of the image
const auto origin_x = test_width * 0.5;
const auto origin_y = test_height * 0.5;
ctx.translate(origin_x, origin_y);

Draw a rotated glyph with clipping:

// Rotate everything around the centre 
const auto degrees = 35.0;
static const constexpr double to_radians = 3.1415926535 / 180;
ctx.rotate(degrees * to_radians); // <<-------- ROTATE

// Place an arbitrary clipping rect over the origin
const BLRect clip_rect{-30, -30, 60, 60};
ctx.clipToRect(clip_rect); // <<--------------- CLIP

// Position the glyph centrally within its design grid over the origin
ctx.save();
ctx.translate(-design_grid.w * 0.5, -design_grid.h * 0.5);

// Draw the glyph
BLGlyphBuffer gb;
gb.setUtf8Text("@");
font.shape(gb);
ctx.fillGlyphRun(
  BLPoint(0, design_ascent), // glyph geometry is relative to the font baseline
  font,
  gb.glyphRun(),
  BLRgba32(255, 255, 255, 255));

// Draw design grid for reference
ctx.setStrokeStyle(BLRgba32(0, 128, 255, 255));
ctx.strokeRect(design_grid);
ctx.strokeLine(BLLine{0, 0, design_grid.w, design_grid.h});
ctx.strokeLine(BLLine{0, design_grid.h, design_grid.w, 0});
ctx.restore();

// Draw clipping rect for reference
ctx.setStrokeStyle(BLRgba32(255, 0, 0, 255));
ctx.strokeRect(clip_rect);

Results:

  1. With no rotation and no clipping:
    test

  2. With no rotation, but with clipping:
    test

  3. With rotation, and no clipping:
    test

  4. With rotation, and with clipping:
    test

The meaning of the coloured rectangles are as follows:

  • Image border in yellow
  • Font grid in blue
  • clipping rectangle in red

You can see in the last image the clipping rectangle is not transformed correctly and the result is incorrect clipping. This demo was compiled and run under MinGW-w64, GCC.

Thanks,
Nathan

@squrky
Copy link
Author

squrky commented Feb 28, 2024

Apologies, this problem is actually not about glyphs, as the above images show the clipping is also not being correctly applied to simple lines and rectangle strokes.

This simplified code listing demonstrates:

// Outer and inner rectangles
const BLRect outer_rect{0, 0, 50, 100};
const BLRect inner_rect{-30, -30, 60, 60};

// Make test image and context
const auto test_width = 200;
const auto test_height = 200;
BLImage img(test_width, test_height, BL_FORMAT_PRGB32);
BLContext ctx(img);
ctx.clearAll();

// Draw image boundary
ctx.setStrokeStyle(BLRgba32{255, 255, 0, 255});
ctx.strokeRect(BLRect{0, 0, test_width, test_height});

// Move the origin to the centre of the image
const auto origin_x = test_width * 0.5;
const auto origin_y = test_height * 0.5;
ctx.translate(origin_x, origin_y);

// Rotate everything around the centre 
const auto degrees = 35.0;
static const constexpr double to_radians = 3.1415926535 / 180;
ctx.rotate(degrees * to_radians); // <<-------- ROTATE

// Clip with inner rectangle
ctx.clipToRect(inner_rect); // <<--------------- CLIP

// Position rectangles over the origin
ctx.save();
ctx.translate(-outer_rect.w * 0.5, -outer_rect.h * 0.5);

// Draw outer rect and lines
ctx.setStrokeStyle(BLRgba32(0, 128, 255, 255));
ctx.strokeRect(outer_rect);
ctx.strokeLine(BLLine{0, 0, outer_rect.w, outer_rect.h});
ctx.strokeLine(BLLine{0, outer_rect.h, outer_rect.w, 0});
ctx.restore();

// Draw inner rect used for clipping
ctx.setStrokeStyle(BLRgba32(255, 0, 0, 255));
ctx.strokeRect(inner_rect);

And the result:
test

The actual clipping is performed inside what seems suspiciously similar to the screen-space extents of the transformed clipping rect.

@kobalicek kobalicek added the bug label Mar 3, 2024
@kobalicek
Copy link
Member

This is an unimplemented feature at the moment. It's on the roadmap, I will focus on this after new stroking engine is merged.

@squrky
Copy link
Author

squrky commented Mar 4, 2024

Thanks. Out of interest, do you publish your development roadmap anywhere?

@kobalicek
Copy link
Member

Yeah the current roadmap is here:

In general all features mentioned on roadmap are planned, and those blue (in progress) have high priority and the work has already started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants