freetype
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[ft] Fat strokes getting cropped


From: Erik Möller
Subject: [ft] Fat strokes getting cropped
Date: Sun, 7 Dec 2008 22:37:18 +0100

Hi,

I'm using freetype 2.3.7 to render a color glyph that has another color
stroke to a bitmap via direct rendering. It works well, but I've noticed
that when the stroke width grows beyond a certain limit the spans appear to
get cropped on the right side.

I may have overlooked something in my code, but anyway I'm out of ideas
right now so any help is appreciated. I've cut it down as much as possible
so it's just a bit over 200 lines now and it dumps two TGAs. I've only
compiled it under VC so apologies if there's something not portable.

/Erik Möller

------ main.cpp ------

#include <memory.h>
#include <fstream>
#include <vector>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H

typedef unsigned char byte;
typedef unsigned short word;

#pragma pack(push)
#pragma pack(1)               // Dont pad the following struct.

struct TGAHeader
{
  byte  idLength,           // Length of optional identification sequence.
        paletteType,        // Indicates whether a palette is present.
        imageType;          // Image data type (e.g., uncompressed RGB).
  word  firstPaletteEntry,  // First palette index, if present.
        numPaletteEntries;  // Number of palette entries, if present.
  byte  paletteBits;        // Number of bits per palette entry.
  word  x,                  // Horizontal pixel coordinate of lower left of
image.
        y,                  // Vertical pixel coordinate of lower left of
image.
        width,              // Image width in pixels.
        height;             // Image height in pixels.
  byte  depth,              // Image color depth (bits per pixel).
        descriptor;         // Image attribute flags.
};

#pragma pack(pop)

void WriteTGASimple(const char *fileName, byte *data32, unsigned int width,
unsigned int height)
{
  std::ofstream file(fileName, std::ios::binary);
  if (file)
  {
    TGAHeader header;
    memset(&header, 0, sizeof(header));
    header.width = width;
    header.height = height;
    header.descriptor = 0x20;
    header.imageType = 2;
    header.depth = 32;

    file.write((char *)&header, sizeof(header));
    file.write((char *)data32, 4 * width * height);
    file.close();
  }
}

struct Pixel32
{
  Pixel32() : r(0), g(0x80), b(0), a(0) { }
  Pixel32(byte red, byte green, byte blue, byte alpha) : r(red), g(green),
b(blue), a(alpha) { }

  byte r,g,b,a;
};

struct Image
{
  Image(int w, int h) : width(w), height(h) { data = new Pixel32[w * h]; }

  Pixel32 *data;
  unsigned int width,
               height;
};

struct Span
{
  Span() { }
  Span(int _x, int _y, int _width, int _coverage) : x(_x), y(_y),
width(_width), coverage(_coverage) { }

  int y,
      x,
      width,
      coverage;
};

typedef std::vector<Span> Spans;

struct Vec2
{
  Vec2() { }
  Vec2(int a, int b) : x(a), y(b) { }

  int x, y;
};

struct Rect
{
  Rect() { }
  Rect(int left, int top, int right, int bottom) : xmin(left), xmax(right),
ymin(top), ymax(bottom) { }

  int Width() const { return xmax - xmin + 1; }
  int Height() const { return ymax - ymin + 1; }

  void Include(const Vec2 &r)
  {
    xmin = __min(xmin, r.x);
    ymin = __min(ymin, r.y);
    xmax = __max(xmax, r.x);
    ymax = __max(ymax, r.y);
  }

  int xmin, 
      ymin, 
      xmax, 
      ymax;
};


void RasterCallback(const int y, const int count, const FT_Span * const
spans, void * const user) 
{
  Spans *sptr = (Spans *)user;
  for (int i = 0; i < count; ++i) 
    sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
}

void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans
*spans) 
{
  FT_Raster_Params params;
  memset(&params, 0, sizeof(params));
  params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
  params.gray_spans = RasterCallback;
  params.user = spans;

  FT_Outline_Render(library, outline, &params);
}


Image *RenderGlyph(FT_Library &library, wchar_t ch, FT_Face &face, int size,
int outlineWidth)
{
  if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
    return NULL;

  if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) != 0)
    return NULL;

  FT_UInt gindex = FT_Get_Char_Index(face, ch);
  if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) != 0)
    return NULL;

  Image *img = NULL;

  if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
  {
    Spans spans,
          outlineSpans;

    // Render the glyph to the span list
    RenderSpans(library, &face->glyph->outline, &spans);

    // Set up a stroker and render the outline to the span list
    FT_Stroker stroker;
    FT_Stroker_New(library, &stroker);
    FT_Stroker_Set(stroker, outlineWidth * 64, FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND, 0);

    FT_Glyph glyph;
    if (FT_Get_Glyph(face->glyph, &glyph) != 0)
      return NULL;

    FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
    if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
    {
      FT_Outline *o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
      RenderSpans(library, o, &outlineSpans);
    }

    FT_Stroker_Done(stroker);
    FT_Done_Glyph(glyph);

    // Find the bounding rect for both the glyph and the outline
    Rect rect;
    if (!spans.empty())
      rect = Rect(spans.front().x, spans.front().y, spans.front().x,
spans.front().y);
    for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
    {
      rect.Include(Vec2(s->x, s->y));
      rect.Include(Vec2(s->x + s->width - 1, s->y));
    }
    for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end();
++s)
    {
      rect.Include(Vec2(s->x, s->y));
      rect.Include(Vec2(s->x + s->width - 1, s->y));
    }

    // Create an image and draw the glyph and the outline to it.
    img = new Image(rect.Width(), rect.Height());
    Pixel32 *pxl = (Pixel32 *)img->data;

    for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end();
++s)
      for (int w = 0; w < s->width; ++w)
        pxl[(int)((img->height - 1 - (s->y - rect.ymin)) * img->width + s->x
- rect.xmin + w)] = Pixel32(0, 0, 0, s->coverage);

    for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
      for (int w = 0; w < s->width; ++w)
      {
        Pixel32 &dst = pxl[(int)((img->height - 1 - (s->y - rect.ymin)) *
img->width + s->x - rect.xmin + w)];
        Pixel32 src = Pixel32(0xff, 0xff, 0xff, s->coverage);
        dst.r = (int)(dst.r + ((src.r - dst.r) * src.a) / 255.0f);
        dst.g = (int)(dst.g + ((src.g - dst.g) * src.a) / 255.0f);
        dst.b = (int)(dst.b + ((src.b - dst.b) * src.a) / 255.0f);
        dst.a = __min(255, dst.a + src.a);
      }
  }

  return img;
}


void main()
{
  FT_Library library;
  FT_Init_FreeType(&library);

  std::ifstream in("arial.ttf", std::ios::binary);
  if (in)
  {
    in.seekg(0, std::ios::end);
    int sz = in.tellg();
    in.seekg(0, std::ios::beg);
    unsigned char *buf = new unsigned char[sz];
    in.read((char *)buf, sz);

    FT_Face face;
    FT_New_Memory_Face(library, buf, sz, 0, &face);

    Image *img = RenderGlyph(library, L'D', face, 100, 4);
    WriteTGASimple("ok.tga", (byte *)img->data, img->width, img->height);

    img = RenderGlyph(library, L'D', face, 100, 20);
    WriteTGASimple("not_ok.tga", (byte *)img->data, img->width,
img->height);
  }
}

------ main.cpp ------




reply via email to

[Prev in Thread] Current Thread [Next in Thread]