ecere/gfx/Surface; drivers: Font outline support
authorJerome St-Louis <jerome@ecere.com>
Thu, 23 Oct 2014 18:14:14 +0000 (14:14 -0400)
committerJerome St-Louis <jerome@ecere.com>
Thu, 15 Oct 2015 00:19:23 +0000 (20:19 -0400)
ecere/Makefile
ecere/ecere.epj
ecere/src/gfx/Surface.ec
ecere/src/gfx/drivers/CocoaOpenGLDisplayDriver.ec
ecere/src/gfx/drivers/Direct3D8DisplayDriver.ec
ecere/src/gfx/drivers/Direct3D9DisplayDriver.ec
ecere/src/gfx/drivers/LFBDisplayDriver.ec
ecere/src/gfx/drivers/OpenGLDisplayDriver.ec
ecere/src/gfx/drivers/XDisplayDriver.ec
ecere/src/gfx/drivers/edtaa3func.ec [new file with mode: 0644]

index 1ede552..71946e9 100644 (file)
@@ -109,6 +109,7 @@ _ECSOURCES2 = \
        $(if $(WINDOWS_TARGET),src/gfx/drivers/Win32ConsoleDisplayDriver.ec,) \
        $(if $(WINDOWS_TARGET),src/gfx/drivers/Win32PrinterDisplayDriver.ec,) \
        $(if $(or $(LINUX_TARGET),$(OSX_TARGET)),src/gfx/drivers/XDisplayDriver.ec,) \
+       src/gfx/drivers/edtaa3func.ec \
        src/gfx/Bitmap.ec \
        src/gfx/BitmapResource.ec \
        src/gfx/Color.ec \
@@ -788,6 +789,9 @@ $(OBJ)XDisplayDriver.sym: src/gfx/drivers/XDisplayDriver.ec
        $(ECP) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) -c src/gfx/drivers/XDisplayDriver.ec -o $(OBJ)XDisplayDriver.sym
 endif
 
+$(OBJ)edtaa3func.sym: src/gfx/drivers/edtaa3func.ec
+       $(ECP) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) -c $(call quote_path,src/gfx/drivers/edtaa3func.ec) -o $(call quote_path,$@)
+
 $(OBJ)Bitmap.sym: src/gfx/Bitmap.ec
        $(ECP) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) -c src/gfx/Bitmap.ec -o $(OBJ)Bitmap.sym
 
@@ -1200,6 +1204,9 @@ $(OBJ)XDisplayDriver.c: src/gfx/drivers/XDisplayDriver.ec $(OBJ)XDisplayDriver.s
        $(ECC) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) -c src/gfx/drivers/XDisplayDriver.ec -o $(OBJ)XDisplayDriver.c -symbols $(OBJ)
 endif
 
+$(OBJ)edtaa3func.c: src/gfx/drivers/edtaa3func.ec $(OBJ)edtaa3func.sym | $(SYMBOLS)
+       $(ECC) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) $(FVISIBILITY) -c $(call quote_path,src/gfx/drivers/edtaa3func.ec) -o $(call quote_path,$@) -symbols $(OBJ)
+
 $(OBJ)Bitmap.c: src/gfx/Bitmap.ec $(OBJ)Bitmap.sym | $(SYMBOLS)
        $(ECC) $(CFLAGS) $(CECFLAGS) $(ECFLAGS) $(PRJ_CFLAGS) -c src/gfx/Bitmap.ec -o $(OBJ)Bitmap.c -symbols $(OBJ)
 
@@ -1678,6 +1685,9 @@ $(OBJ)XDisplayDriver.o: $(OBJ)XDisplayDriver.c
        $(CC) $(CFLAGS) $(PRJ_CFLAGS) -c $(OBJ)XDisplayDriver.c -o $(OBJ)XDisplayDriver.o
 endif
 
+$(OBJ)edtaa3func.o: $(OBJ)edtaa3func.c
+       $(CC) $(CFLAGS) $(PRJ_CFLAGS) $(FVISIBILITY) -c $(call quote_path,$(OBJ)edtaa3func.c) -o $(call quote_path,$@)
+
 $(OBJ)Bitmap.o: $(OBJ)Bitmap.c
        $(CC) $(CFLAGS) $(PRJ_CFLAGS) -c $(OBJ)Bitmap.c -o $(OBJ)Bitmap.o
 
index 58a52d4..282dde6 100644 (file)
@@ -1509,6 +1509,26 @@ from wherever you obtained them.
                                  ]
                               }
                            ]
+                        },
+                        {
+                           "FileName" : "edtaa3func.ec",
+                           "Options" : {
+                              "ExcludeFromBuild" : false
+                           },
+                           "Configurations" : [
+                              {
+                                 "Name" : "Vanilla",
+                                 "Options" : {
+                                    "ExcludeFromBuild" : true
+                                 }
+                              },
+                              {
+                                 "Name" : "Bootstrap",
+                                 "Options" : {
+                                    "ExcludeFromBuild" : true
+                                 }
+                              }
+                           ]
                         }
                      ],
                      "Options" : {
index 1368a29..e5d4598 100644 (file)
@@ -109,6 +109,12 @@ public enum AlphaWriteMode
    blend
 };
 
+public struct FontOutline
+{
+   float size, fade;
+   ColorAlpha color;
+};
+
 public class Surface
 {
 public:
@@ -131,6 +137,7 @@ private:
    bool blend;
    bool writeColor;
    ColorAlpha blitTint;
+   FontOutline outline;
 
    blitTint = white;
 
@@ -162,6 +169,11 @@ public:
          return ((LFBSurface)driverData).bitmap;
       }
    }
+   property FontOutline outline
+   {
+      set { outline = value; }
+      get { value = outline; }
+   }
 
    ColorAlpha GetPixel(int x, int y)
    {
index 7edf428..91d1a52 100644 (file)
@@ -51,6 +51,7 @@ class SurfaceData : struct
    bool opaqueText;
    int  xOffset;
    bool writingText;
+   bool writingOutline;
 
    float foreground[4], background[4], bitmapMult[4];
 };
index 0683b5e..5b6c304 100644 (file)
@@ -76,6 +76,7 @@ static class D3D8Surface : struct
    bool opaqueText;
    int xOffset;
    bool writingText;
+   bool writingOutline;
 
    ColorAlpha background;
 };
index 38eeeb8..46f7b43 100644 (file)
@@ -82,6 +82,7 @@ static class D3DSurface : struct
    bool opaqueText;
    int xOffset;
    bool writingText;
+   bool writingOutline;
 
    ColorAlpha background;
 };
index b427193..c0a1ffb 100644 (file)
@@ -62,6 +62,92 @@ import "Direct3D9DisplayDriver"
 
 #if !defined(ECERE_NOTRUETYPE)
 
+#if !defined(ECERE_VANILLA)
+import "edtaa3func"
+
+static void ComputeOutline(byte *out, byte *src, uint w, uint h, float size, float fade)
+{
+   uint i, numPixels = w * h;
+   short * distx = new short[2 * numPixels], * disty = distx + numPixels;
+   float * data = new0 float[4 * numPixels], * gx = data + numPixels, * gy = gx + numPixels, * dist = gy + numPixels;
+   float rb = Max(1.5f, size), ra = rb - (rb-1)*fade - 1;
+   float inv_rw = 1/(rb-ra);
+
+   for(i = 0; i < numPixels; i++)
+      data[i] = src[i] / 255;
+
+   computegradient(data, w, h, gx, gy);
+   edtaa3(data, gx, gy, w, h, distx, disty, dist);
+
+   for(i = 0; i < numPixels; i++)
+   {
+      float value = 1 - Max(0.0f, Min(1.0f, (dist[i]-ra)*inv_rw));
+      out[i] = (byte)(255 * value * value);
+   }
+   delete distx;
+   delete data;
+}
+
+static void BlitOutline(byte * dst, int dx, int dy, int w, int h, byte * src, int sx, int sy, int sw, int sh, int srcStride)
+{
+   sh = Min(h - dy, sh);
+   sw = Min(w - dx, sw);
+   if(sw > 0 && sh > 0)
+   {
+      int y;
+      for(y = 0; y < sh; y++)
+         memcpy(dst + w * (dy+y) + dx, src + srcStride * (sy+y) + sx, sw);
+   }
+}
+
+static void MeasureOutline(byte * image, int w, int h, int * _x1, int * _y1, int * _x2, int * _y2)
+{
+   int x1 = MAXINT, y1 = MAXINT, x2 = MININT, y2 = MININT;
+   int x, y;
+   for(x = 0; x < w && x1 == MAXINT; x++)
+   {
+      for(y = 0; y < h; y++)
+         if(image[(y*w)+x])
+         {
+            x1 = x;
+            break;
+         }
+   }
+   for(x = w-1; x >= 0 && x2 == MININT; x--)
+   {
+      for(y = 0; y < h; y++)
+         if(image[(y*w)+x])
+         {
+            x2 = x;
+            break;
+         }
+   }
+   for(y = 0; y < h && y1 == MAXINT; y++)
+   {
+      for(x = 0; x < w; x++)
+         if(image[(y*w)+x])
+         {
+            y1 = y;
+            break;
+         }
+   }
+   for(y = h-1; y >= 0 && y2 == MININT; y--)
+   {
+      for(x = 0; x < w; x++)
+         if(image[(y*w)+x])
+         {
+            y2 = y;
+            break;
+         }
+   }
+   *_x1 = x1;
+   *_y1 = y1;
+   *_x2 = x2;
+   *_y2 = y2;
+}
+
+#endif
+
 #define MAX_FONT_LINK_ENTRIES   10
 
 static HB_Script theCurrentScript;
@@ -348,6 +434,8 @@ class GlyphPack : BTNode
 {
    GlyphInfo glyphs[256];
    Bitmap bitmap { };
+   BinaryTree outlines { };
+   int cellWidth, cellHeight;
 
    void Render(Font font, int startFontEntry, DisplaySystem displaySystem)
    {
@@ -471,11 +559,17 @@ class GlyphPack : BTNode
          maxHeight = Max(maxHeight, ((faces[c]->glyph->metrics.height + 64 + (64 - (faces[c]->glyph->metrics.height & 0x3F))) >> 6));
          //maxHeight = Max(maxHeight, ((faces[c]->glyph->metrics.height) >> 6));
       }
-      cellWidth = maxWidth;
-      cellHeight = maxHeight;
+      this.cellWidth = cellWidth = maxWidth;
+      this.cellHeight = cellHeight = maxHeight;
+
+      width = maxWidth * 16;
+      height = maxHeight * 8;
 
-      width = pow2i(maxWidth * 16);
-      height = pow2i(maxHeight * 8);
+      if(true)
+      {
+         width = pow2i(height * 16);
+         height = pow2i(height * 8);
+      }
 
       if(bitmap.Allocate(null, width, height, 0, pixelFormatAlpha, false /*true*/))
       {
@@ -606,6 +700,7 @@ class GlyphPack : BTNode
 
          if(displaySystem && displaySystem.pixelFormat != pixelFormat4) // TODO: Add none PixelFormat
          {
+            bitmap.keepData = true; // For outlines
             displaySystem.Lock();
 #if defined(__WIN32__)
             // Is this check still required?
@@ -619,6 +714,167 @@ class GlyphPack : BTNode
       }
 #endif
    }
+
+   void RenderOutline(GlyphPack outline, Font font, DisplaySystem displaySystem)
+   {
+#if !defined(ECERE_NOTRUETYPE) && !defined(ECERE_VANILLA)
+      unichar c;
+      int pCellWidth = this.cellWidth, pCellHeight = this.cellHeight;
+      int cellWidth, cellHeight;
+      int width, height;
+      uintptr key = outline.key;
+      float outlineSize = (float)(key >> 16);
+      float fade = ((uint32)key & 0xFFFF) / 255.f;
+      GlyphInfo * widest = null, * highest = null;
+      uint widestIndex = 0, highestIndex = 0;
+      GlyphInfo * glyph;
+      int minX1 = MAXINT, minY1 = MAXINT;
+      int maxX2 = MININT, maxY2 = MININT;
+      int timesBigger = 2;
+      byte * bigger = new byte[pCellWidth * pCellHeight * timesBigger*timesBigger];
+      byte * field = new byte[pCellWidth * pCellHeight * timesBigger*timesBigger];
+      int ox, oy;
+
+      // Test biggest glyphs to determine cell width & height:
+      for(c = 0; c < 128; c++)
+      {
+         glyph = &glyphs[c];
+         if(glyph->w > (widest ? widest->w : 0))
+            widest = glyph, widestIndex = c;
+         if(glyph->h > (highest ? highest->h : 0))
+            highest = glyph, highestIndex = c;
+      }
+
+      cellWidth = 0;
+      cellHeight = 0;
+      for(glyph = widest; glyph; glyph = (glyph == widest && glyph != highest) ? highest : null)
+      {
+         int index = (glyph == widest) ? widestIndex : highestIndex;
+         int x = (index & 0xF) * pCellWidth, y = (index >> 4) * pCellHeight;
+         int w = pCellWidth * timesBigger, h = pCellHeight * timesBigger;
+         int x1,y1,x2,y2;
+
+         memset(bigger, 0, w * h);
+         BlitOutline(bigger, (w - pCellWidth)/2, (h - pCellHeight)/2, w, h, bitmap.picture, x, y, pCellWidth, pCellHeight, bitmap.width);
+         ComputeOutline(field, bigger, w, h, outlineSize, fade);
+         MeasureOutline(field, w, h, &x1, &y1, &x2, &y2);
+         minX1 = Min(minX1, x1);
+         minY1 = Min(minY1, y1);
+         maxX2 = Max(maxX2, x2);
+         maxY2 = Max(maxY2, y2);
+      }
+      {
+         int x1 = (timesBigger*pCellWidth - pCellWidth)  / 2,  x2 = x1 + pCellWidth-1;
+         int y1 = (timesBigger*pCellHeight - pCellHeight) / 2,  y2 = y1 + pCellHeight-1;
+         ox = -Max(0, x1 - minX1);
+         oy = -Max(0, y1 - minY1);
+         cellWidth  = pCellWidth  - ox + Max(0, maxX2 - x2);
+         cellHeight = pCellHeight - oy + Max(0, maxY2 - y2);
+      }
+
+      width = cellWidth * 16;
+      height = cellHeight * 8;
+      if(true) //TEXTURES_MUST_BE_POWER_OF_2)
+      {
+          width = pow2i(width);
+          height = pow2i(height);
+      }
+
+      if(outline.bitmap.Allocate(null, width, height, 0, pixelFormatAlpha, false))
+      {
+         Bitmap bitmap = outline.bitmap;
+         byte * picture = (byte *)bitmap.picture;
+         memset(picture, 0, width * height);
+
+         for(c = 0; c < 128; c++)
+         {
+            GlyphInfo * glyph = &outline.glyphs[c];
+            int x1 = MAXINT, y1 = MAXINT, x2 = MININT, y2 = MININT;
+            int w = 0, h = 0;
+            memset(bigger, 0, cellWidth * cellHeight);
+            BlitOutline(bigger, -ox, -oy, cellWidth, cellHeight,
+                 this.bitmap.picture,
+                 (c & 0xF) * pCellWidth, (c >> 4) * pCellHeight,
+                 pCellWidth, pCellHeight,
+                 this.bitmap.width);
+
+            // Don't waste time on empty glyphs
+            if(glyphs[c].w)
+            {
+               ComputeOutline(field, bigger, cellWidth, cellHeight, outlineSize, fade);
+               MeasureOutline(field, cellWidth, cellHeight, &x1, &y1, &x2, &y2);
+               if(x2 > x1) w = x2-x1+1;
+               if(y2 > y1) h = y2-y1+1;
+            }
+            else
+               memset(field, 0, cellWidth * cellHeight);
+
+            glyph->x = (c & 0xF) * cellWidth;
+            glyph->y = (c >> 4)  * cellHeight;
+            BlitOutline(picture, glyph->x, glyph->y, width, height, field, 0, 0, cellWidth, cellHeight, cellWidth);
+
+            glyph->glyphNo = glyphs[c].glyphNo;
+            glyph->scale = glyphs[c].scale;
+            glyph->left = glyphs[c].left + ox;
+            glyph->top = glyphs[c].top + oy;
+            if(w) { glyph->x += x1; glyph->left += x1; }
+            if(h) { glyph->y += y1; glyph->top  += y1; }
+            glyph->w = w;
+            glyph->h = h;
+            glyph->bx = glyphs[c].bx;
+            glyph->by = glyphs[c].by;
+            glyph->ax = glyphs[c].ax;
+            glyph->ay = glyphs[c].ay;
+         }
+
+   #if 0
+         {
+            int c;
+            char fileName[256];
+            static int fid = 0;
+            for(c = 0; c<256; c++)
+               outline.bitmap.palette[c] = ColorAlpha { 255, { (byte)c,(byte)c,(byte)c } };
+            outline.bitmap.pixelFormat = pixelFormat8;
+
+            /*
+            //strcpy(fileName, faceName);
+            if(flags)
+               strcat(fileName, "Bold");
+            */
+            sprintf(fileName, "font%d", fid++);
+            ChangeExtension(fileName, "pcx", fileName);
+
+            outline.bitmap.Save(fileName, null, 0);
+            outline.bitmap.pixelFormat = pixelFormatAlpha;
+         }
+
+         /*{
+            static int num = 0;
+            char fileName[MAX_LOCATION];
+
+            sprintf(fileName, "template%03d.png", num);
+            bitmap.Save(fileName, null, 0);
+            sprintf(fileName, "outline%03d.png", num++);
+            outline.bitmap.Save(fileName, null, 0);
+         }*/
+   #endif
+         if(displaySystem && displaySystem.pixelFormat != pixelFormat4) // TODO: Add none PixelFormat
+         {
+            displaySystem.Lock();
+#if defined(__WIN32__)
+            // Is this check still required?
+            if(displaySystem.driver == class(OpenGLDisplayDriver) ||
+               displaySystem.driver == class(Direct3D8DisplayDriver) ||
+               displaySystem.driver == class(Direct3D9DisplayDriver))
+#endif
+               bitmap.MakeDD(displaySystem);
+            displaySystem.Unlock();
+         }
+      }
+      delete bigger;
+      delete field;
+#endif
+   }
 }
 
 #if !defined(ECERE_NOTRUETYPE)
@@ -783,6 +1039,7 @@ public:
    bool opaqueText;
    int xOffset;
    bool writingText;
+   bool writingOutline;
 
    Bitmap bitmap;
 
@@ -3184,11 +3441,12 @@ public class LFBDisplayDriver : DisplayDriver
    {
       if(font && font.fontEntries && font.fontEntries[0])
       {
+         LFBSurface lfbSurface = surface ? surface.driverData : null;
          int previousGlyph = 0;
          FT_Face previousFace = 0;
          int c, nb, glyphIndex = 0;
-         unichar lastPack = 0;
-         GlyphPack pack = font.asciiPack;
+         unichar lastPack = lfbSurface && lfbSurface.writingOutline ? -1 : 0;
+         GlyphPack pack = font.asciiPack, outline = null;
          int wc = 0;
          uint * glyphs = null;
          int numGlyphs = 0;
@@ -3511,11 +3769,24 @@ public class LFBDisplayDriver : DisplayDriver
                }
                pack.bitmap.alphaBlend = true;
                lastPack = packNo;
+#if !defined(ECERE_VANILLA)
+               if(lfbSurface && lfbSurface.writingOutline)
+               {
+                  uint outlineNo = (((uint)surface.outline.size) << 16) | (uint16)(Min(surface.outline.fade, 257.0f) * 255);
+                  outline = (GlyphPack)pack.outlines.Find(outlineNo);
+                  if(!outline)
+                  {
+                     outline = { key = outlineNo };
+                     pack.outlines.Add(outline);
+                     pack.RenderOutline(outline, font, displaySystem);
+                  }
+               }
+#endif
             }
             if(pack)
             {
                int index = rightToLeft ? (glyphIndex + 1) : (glyphIndex-1);
-               GlyphInfo * glyph = &pack.glyphs[glyphNo & 0x7F];
+               GlyphInfo * glyph = &(outline ? outline : pack).glyphs[glyphNo & 0x7F];
 
                int ax = (int)((numGlyphs ? shaper_item.advances[index] : glyph->ax) * glyph->scale);
                int offset = numGlyphs ? shaper_item.offsets[index].x : 0;
@@ -3538,7 +3809,7 @@ public class LFBDisplayDriver : DisplayDriver
                previousFace = curFontEntry.face;
 
                if(callback)
-                  callback(surface, display, ((*x) >> 6), y + (oy >> 6), glyph, pack.bitmap);
+                  callback(surface, display, ((*x) >> 6), y + (oy >> 6), glyph, (outline ? outline : pack).bitmap);
                *x += ax;
             }
             if(numGlyphs && (rightToLeft ? (glyphIndex < 0) : (glyphIndex == numGlyphs)))
index 34460b4..1ba94c3 100644 (file)
@@ -1277,6 +1277,7 @@ class OGLSurface : struct
    bool opaqueText;
    int xOffset;
    bool writingText;
+   bool writingOutline;
 
    float foreground[4], background[4], bitmapMult[4];
 } OGLSurface;
@@ -2348,9 +2349,15 @@ class OpenGLDisplayDriver : DisplayDriver
    {
       bool result = false;
       OGLSystem oglSystem = displaySystem.driverData;
+      Bitmap convBitmap = bitmap;
+      if(bitmap.keepData)
+      {
+         convBitmap = { };
+         convBitmap.Copy(bitmap);
+      }
 
       // Pre process the bitmap... First make it 32 bit
-      if(/*bitmap.pixelFormat == pixelFormatRGBA || */bitmap.Convert(null, pixelFormat888, null))
+      if(/*bitmap.pixelFormat == pixelFormatRGBA || */convBitmap.Convert(null, pixelFormat888, null))
       {
          int c, level;
          uint w = bitmap.width, h = bitmap.height;
@@ -2376,11 +2383,11 @@ class OpenGLDisplayDriver : DisplayDriver
             {
                // ((ColorRGBA *)bitmap.picture)[c] = ((ColorAlpha *)bitmap.picture)[c];
                // TODO:
-               ColorAlpha color = ((ColorAlpha *)bitmap.picture)[c];
-               ((ColorRGBA *)bitmap.picture)[c] = ColorRGBA { color.color.r, color.color.g, color.color.b, color.a };
+               ColorAlpha color = ((ColorAlpha *)convBitmap.picture)[c];
+               ((ColorRGBA *)convBitmap.picture)[c] = ColorRGBA { color.color.r, color.color.g, color.color.b, color.a };
             }
          }
-         bitmap.pixelFormat = pixelFormat888;
+         // convBitmap.pixelFormat = pixelFormat888;
 
          glGetError();
          glGenTextures(1, &glBitmap);
@@ -2413,10 +2420,10 @@ class OpenGLDisplayDriver : DisplayDriver
             if(bitmap.width != w || bitmap.height != h)
             {
                mipMap = Bitmap { };
-               if(mipMap.Allocate(null, w, h, w, bitmap.pixelFormat, false))
+               if(mipMap.Allocate(null, w, h, w, convBitmap.pixelFormat, false))
                {
                   Surface mipSurface = mipMap.GetSurface(0,0,null);
-                  mipSurface.Filter(bitmap, 0,0,0,0, w, h, bitmap.width, bitmap.height);
+                  mipSurface.Filter(convBitmap, 0,0,0,0, w, h, convBitmap.width, convBitmap.height);
                   delete mipSurface;
                }
                else
@@ -2426,7 +2433,7 @@ class OpenGLDisplayDriver : DisplayDriver
                }
             }
             else
-               mipMap = bitmap;
+               mipMap = convBitmap;
 
             if(result)
             {
@@ -2445,13 +2452,12 @@ class OpenGLDisplayDriver : DisplayDriver
                   result = false;
                }
             }
-            if(mipMap != bitmap)
+            if(mipMap != convBitmap)
                delete mipMap;
             if(!mipMaps) break;
          }
 
-         if(!bitmap.keepData)
-            bitmap.driver.FreeBitmap(bitmap.displaySystem, bitmap);
+         convBitmap.driver.FreeBitmap(convBitmap.displaySystem, convBitmap);
          bitmap.driverData = (void *)(uintptr)glBitmap;
          bitmap.driver = displaySystem.driver;
 
@@ -3119,6 +3125,18 @@ class OpenGLDisplayDriver : DisplayDriver
       oglSurface.writingText = true;
 
       glEnable(GL_TEXTURE_2D);
+
+      if(surface.outline.size)
+      {
+         ColorAlpha outlineColor = surface.outline.color;
+         glColor4ub(outlineColor.color.r, outlineColor.color.g, outlineColor.color.b, outlineColor.a);
+         //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+         //glEnable(GL_BLEND);
+
+         oglSurface.writingOutline = true;
+         ((subclass(DisplayDriver))class(LFBDisplayDriver)).WriteText(display, surface, x, y, text, len);
+         oglSurface.writingOutline = false;
+      }
       glColor4fv(oglSurface.foreground);
 
       ((subclass(DisplayDriver))class(LFBDisplayDriver)).WriteText(display, surface, x, y, text, len);
index 6d91801..01497c9 100644 (file)
@@ -70,6 +70,7 @@ class XSurface : struct
    bool opaqueText;
    int xOffset;
    bool writingText;
+   bool writingOutline;
 
    ColorAlpha foreground, background;
    bool opaque;
diff --git a/ecere/src/gfx/drivers/edtaa3func.ec b/ecere/src/gfx/drivers/edtaa3func.ec
new file mode 100644 (file)
index 0000000..9546262
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * edtaa3()
+ *
+ * Sweep-and-update Euclidean distance transform of an
+ * image. Positive pixels are treated as object pixels,
+ * zero or negative pixels are treated as background.
+ * An attempt is made to treat antialiased edges correctly.
+ * The input image must have pixels in the range [0,1],
+ * and the antialiased image should be a box-filter
+ * sampling of the ideal, crisp edge.
+ * If the antialias region is more than 1 pixel wide,
+ * the result from this transform will be inaccurate.
+ *
+ * By Stefan Gustavson (stefan.gustavson@gmail.com).
+ *
+ * Originally written in 1994, based on a verbal
+ * description of the SSED8 algorithm published in the
+ * PhD dissertation of Ingemar Ragnemalm. This is his
+ * algorithm, I only implemented it in C.
+ *
+ * Updated in 2004 to treat border pixels correctly,
+ * and cleaned up the code to improve readability.
+ *
+ * Updated in 2009 to handle anti-aliased edges.
+ *
+ * Updated in 2011 to avoid a corner case infinite loop.
+ *
+ * Updated 2012 to change license from LGPL to MIT.
+ */
+
+/*
+ Copyright (C) 2009-2012 Stefan Gustavson (stefan.gustavson@gmail.com)
+ The code in this file is distributed under the MIT license:
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+#ifdef BUILDING_ECERE_COM
+import "instance"
+#endif
+
+ // Note: Changed double to float from original version
+
+/*
+ * Compute the local gradient at edge pixels using convolution filters.
+ * The gradient is computed only at edge pixels. At other places in the
+ * image, it is never used, and it's mostly zero anyway.
+ */
+void computegradient(float *img, int w, int h, float *gx, float *gy)
+{
+    int i,j,k;
+    float glength;
+#define SQRT2 1.4142136f
+    for(i = 1; i < h-1; i++) { // Avoid edges where the kernels would spill over
+        for(j = 1; j < w-1; j++) {
+            k = i*w + j;
+            if((img[k]>0.0) && (img[k]<1.0f)) { // Compute gradient for edge pixels only
+                gx[k] = -img[k-w-1] - SQRT2*img[k-1] - img[k+w-1] + img[k-w+1] + SQRT2*img[k+1] + img[k+w+1];
+                gy[k] = -img[k-w-1] - SQRT2*img[k-w] - img[k+w-1] + img[k-w+1] + SQRT2*img[k+w] + img[k+w+1];
+                glength = gx[k]*gx[k] + gy[k]*gy[k];
+                if(glength > 0.0f) { // Avoid division by zero
+                    glength = (float)sqrt(glength);
+                    gx[k]=gx[k]/glength;
+                    gy[k]=gy[k]/glength;
+                }
+            }
+        }
+    }
+    // TODO: Compute reasonable values for gx, gy also around the image edges.
+    // (These are zero now, which reduces the accuracy for a 1-pixel wide region
+       // around the image edge.) 2x2 kernels would be suitable for this.
+}
+
+/*
+ * A somewhat tricky function to approximate the distance to an edge in a
+ * certain pixel, with consideration to either the local gradient (gx,gy)
+ * or the direction to the pixel (dx,dy) and the pixel greyscale value a.
+ * The latter alternative, using (dx,dy), is the metric used by edtaa2().
+ * Using a local estimate of the edge gradient (gx,gy) yields much better
+ * accuracy at and near edges, and reduces the error even at distant pixels
+ * provided that the gradient direction is accurately estimated.
+ */
+float edgedf(float gx, float gy, float a)
+{
+    float df, glength, temp, a1;
+
+    if ((gx == 0) || (gy == 0)) { // Either A) gu or gv are zero, or B) both
+        df = 0.5f-a;  // Linear approximation is A) correct or B) a fair guess
+    } else {
+        glength = (float)sqrt(gx*gx + gy*gy);
+        if(glength>0) {
+            gx = gx/glength;
+            gy = gy/glength;
+        }
+        /* Everything is symmetric wrt sign and transposition,
+         * so move to first octant (gx>=0, gy>=0, gx>=gy) to
+         * avoid handling all possible edge directions.
+         */
+        gx = (float)fabs(gx);
+        gy = (float)fabs(gy);
+        if(gx<gy) {
+            temp = gx;
+            gx = gy;
+            gy = temp;
+        }
+        a1 = 0.5f*gy/gx;
+        if (a < a1) { // 0 <= a < a1
+            df = 0.5f*(gx + gy) - (float)sqrt(2.0*gx*gy*a);
+        } else if (a < (1.0-a1)) { // a1 <= a <= 1-a1
+            df = (0.5f-a)*gx;
+        } else { // 1-a1 < a <= 1
+            df = -0.5f*(gx + gy) + (float)sqrt(2.0*gx*gy*(1.0-a));
+        }
+    }
+    return df;
+}
+
+float distaa3(float *img, float *gximg, float *gyimg, int w, int c, int xc, int yc, int xi, int yi)
+{
+  float di, df, dx, dy, gx, gy, a;
+  int closest;
+
+  closest = c-xc-yc*w; // Index to the edge pixel pointed to from c
+  a = img[closest];    // Grayscale value at the edge pixel
+  gx = gximg[closest]; // X gradient component at the edge pixel
+  gy = gyimg[closest]; // Y gradient component at the edge pixel
+
+  if(a > 1.0) a = 1.0;
+  if(a < 0.0) a = 0.0; // Clip grayscale values outside the range [0,1]
+  if(a == 0.0) return 1000000.0; // Not an object pixel, return "very far" ("don't know yet")
+
+  dx = (float)xi;
+  dy = (float)yi;
+  di = (float)sqrt(dx*dx + dy*dy); // Length of integer vector, like a traditional EDT
+  if(di==0) { // Use local gradient only at edges
+      // Estimate based on local gradient only
+      df = edgedf(gx, gy, a);
+  } else {
+      // Estimate gradient based on direction to edge (accurate for large di)
+      df = edgedf(dx, dy, a);
+  }
+  return di + df; // Same metric as edtaa2, except at edges (where di=0)
+}
+
+// Shorthand macro: add ubiquitous parameters dist, gx, gy, img and w and call distaa3()
+#define DISTAA(c,xc,yc,xi,yi) (distaa3(img, gx, gy, w, c, xc, yc, xi, yi))
+
+void edtaa3(float *img, float *gx, float *gy, int w, int h, short *distx, short *disty, float *dist)
+{
+  int x, y, i, c;
+  int offset_u, offset_ur, offset_r, offset_rd,
+  offset_d, offset_dl, offset_l, offset_lu;
+  float olddist, newdist;
+  int cdistx, cdisty, newdistx, newdisty;
+  int changed;
+  float epsilon = 1e-3;
+
+  /* Initialize index offsets for the current image width */
+  offset_u = -w;
+  offset_ur = -w+1;
+  offset_r = 1;
+  offset_rd = w+1;
+  offset_d = w;
+  offset_dl = w-1;
+  offset_l = -1;
+  offset_lu = -w-1;
+
+  /* Initialize the distance images */
+  for(i=0; i<w*h; i++) {
+    distx[i] = 0; // At first, all pixels point to
+    disty[i] = 0; // themselves as the closest known.
+    if(img[i] <= 0.0)
+      {
+       dist[i]= 1000000.0; // Big value, means "not set yet"
+      }
+    else if (img[i]<1.0) {
+      dist[i] = edgedf(gx[i], gy[i], img[i]); // Gradient-assisted estimate
+    }
+    else {
+      dist[i]= 0.0; // Inside the object
+    }
+  }
+
+  /* Perform the transformation */
+  do
+    {
+      changed = 0;
+
+      /* Scan rows, except first row */
+      for(y=1; y<h; y++)
+        {
+
+          /* move index to leftmost pixel of current row */
+          i = y*w;
+
+          /* scan right, propagate distances from above & left */
+
+          /* Leftmost pixel is special, has no left neighbors */
+          olddist = dist[i];
+          if(olddist > 0) // If non-zero distance or not set yet
+            {
+             c = i + offset_u; // Index of candidate for testing
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_ur;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+          i++;
+
+          /* Middle pixels have all neighbors */
+          for(x=1; x<w-1; x++, i++)
+            {
+              olddist = dist[i];
+              if(olddist <= 0) continue; // No need to update further
+
+             c = i+offset_l;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_lu;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_u;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_ur;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+
+          /* Rightmost pixel of row is special, has no right neighbors */
+          olddist = dist[i];
+          if(olddist > 0) // If not already zero distance
+            {
+             c = i+offset_l;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_lu;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_u;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty+1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+
+          /* Move index to second rightmost pixel of current row. */
+          /* Rightmost pixel is skipped, it has no right neighbor. */
+          i = y*w + w-2;
+
+          /* scan left, propagate distance from right */
+          for(x=w-2; x>=0; x--, i--)
+            {
+              olddist = dist[i];
+              if(olddist <= 0) continue; // Already zero distance
+
+             c = i+offset_r;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+        }
+
+      /* Scan rows in reverse order, except last row */
+      for(y=h-2; y>=0; y--)
+        {
+          /* move index to rightmost pixel of current row */
+          i = y*w + w-1;
+
+          /* Scan left, propagate distances from below & right */
+
+          /* Rightmost pixel is special, has no right neighbors */
+          olddist = dist[i];
+          if(olddist > 0) // If not already zero distance
+            {
+             c = i+offset_d;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_dl;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                           dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+          i--;
+
+          /* Middle pixels have all neighbors */
+          for(x=w-2; x>0; x--, i--)
+            {
+              olddist = dist[i];
+              if(olddist <= 0) continue; // Already zero distance
+
+             c = i+offset_r;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                 dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_rd;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                 dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_d;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                  dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_dl;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                  dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+          /* Leftmost pixel is special, has no left neighbors */
+          olddist = dist[i];
+          if(olddist > 0) // If not already zero distance
+            {
+             c = i+offset_r;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                  dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_rd;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx-1;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                 dist[i]=(short)newdist;
+                  olddist=newdist;
+                  changed = 1;
+                }
+
+             c = i+offset_d;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx;
+              newdisty = cdisty-1;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                  dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+
+          /* Move index to second leftmost pixel of current row. */
+          /* Leftmost pixel is skipped, it has no left neighbor. */
+          i = y*w + 1;
+          for(x=1; x<w; x++, i++)
+            {
+              /* scan right, propagate distance from left */
+              olddist = dist[i];
+              if(olddist <= 0) continue; // Already zero distance
+
+             c = i+offset_l;
+             cdistx = distx[c];
+             cdisty = disty[c];
+              newdistx = cdistx+1;
+              newdisty = cdisty;
+              newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty);
+              if(newdist < olddist-epsilon)
+                {
+                  distx[i]=(short)newdistx;
+                  disty[i]=(short)newdisty;
+                  dist[i]=(short)newdist;
+                  changed = 1;
+                }
+            }
+        }
+    }
+  while(changed); // Sweep until no more updates are made
+
+  /* The transformation is completed. */
+
+}