2012-08-04 132 views
2

我正在使用Juce库来显示图形的项目。 到目前为止,我一直在使用库的API函数来生成线性和径向渐变,但是这是该库支持的唯一两种类型的渐变。我现在需要生成一种不同类型的渐变,它遵循规则凸多边形的形状。这里的关键词是REGULAR,意思是一个多边形,所有的边具有相同的长度,并且所有的顶点都位于一个圆上。多边形梯度

对于一个五边形的情况下,这里是一个图片,以更好地显示结果我想获得: http://www.filterforge.com/wiki/index.php/Polygonal_Gradient

对于我的申请,我希望能够与任意数量的指定多边形渐变边缘。 (五角形,六角形,八角形等)

由于API的限制,我能够产生所需结果的唯一方法是逐个像素填充表面矩阵,数学计算R的值, G,B,每个像素的分量。

下面是代码我迄今为止:

void render_surface(unsigned char *surface_data, 
        int width, int height, int linestride, 
        int num_vertices, t_rgba *color1, t_rgba *color2) 
{ 
    const double center_x = 0.5 * width; 
    const double center_y = 0.5 * height; 
    const double radius = 0.5 * MIN(width, height); 
    int x, y; 

    for (y = height; --y >= 0;) { 

     uint32_t *line = (uint32_t *)data; 
     data += linestride; 

     const double dy = y - center_y; 

     for (x = width; --x >= 0;) { 

      const double dx = x - center_x; 

      double rho = hypot(dx, dy); 
      rho /= radius; // normalize radius 

      // constrain 
      rho = CLIP(rho, 0.0, 1.0); 

      // interpolate 
      double a = color2->alpha + (color1->alpha - color2->alpha) * rho; 
      double r = color2->red + (color1->red - color2->red ) * rho; 
      double g = color2->green + (color1->green - color2->green) * rho; 
      double b = color2->blue + (color1->blue - color2->blue) * rho; 

      // premultiply alpha 
      r *= a; 
      g *= a; 
      b *= a; 

#if LITTLE_ENDIAN 
      *line++ = ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * a) << 24) // alpha 
        | ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * r) << 16) // red 
        | ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * g) << 8) // green 
        | (unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * b);  // blue 
#else 
      *line++ = ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * b) << 24) // blue 
        | ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * g) << 16) // green 
        | ((unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * r) << 8) // red 
        | (unsigned int)((256.0 * (1.0 - DBL_EPSILON)) * a);  // alpha 
#endif 
     } 
    } 
} 

以上代码生成的径向梯度,同一类型的梯度我可以产生利用一个API函数。不过,这似乎是解决问题的一个很好的起点。

surface_data - 是代表红色,绿色,蓝色和Alpha分量像素强度的8位值矩阵。

num_vertices - 是我们希望我们的多边形渐变具有的顶点数(在单个圆上等间距)。

color1 - 渐变的起始颜色。

color2 - 渐变的结束颜色。

我想知道如何以相同的方式填充曲面,创建多边形渐变而不是径向。

感谢您的任何帮助。

  • 路易吉

再思考这个问题有点...... 如果我们考虑到我们的坐标系中的多边形的中心的原点,把它归结为找到一个方程使得对任意输入点以笛卡尔坐标表示,输出是距离多边形最近边的距离。

我的直觉告诉我,一定有某种封闭形式的解决方案,因为:

了一圈,

rho = sqrt(dx*dx + dy*dy); 

让我们从圆的中心的径向距离,这可能是视为具有无限边的多边形。

对于方形,

fmax(fabs(dx), fabs(dy)); 

使我们从方形,这可以被视为具有4个边的多边形的最近侧的切比雪夫距离。

所以,我认为这两个公式的某种组合应该给中介案例,这将解决最初的问题。

我完全不考虑这些方面吗?

  • 路易吉

回答

1

这大约是我怎么想靠近它......

  • 将在原点 'O' 的多边形的中心。
  • 对于正多边形的给定段内的给定的点“P”,让通过 “O” &“P”的线是“线1”和
  • 让线通过 的含有外边缘多边形段为'Line2'
  • 找到这两行的intesection点'IP'。

现在,P处的色彩分数由P与原点的距离相对于IP到原点的距离来定义。

enter image description here

编辑:我实现了上述算法,这是输出...

enter image description here

EDIT2: 这里的(德尔福)代码

const 
    vertical: TFloat = 3.4e38; 

function Slope(const pt1, pt2: TFloatPoint): single; 
begin 
    if (pt1.X = pt2.X) then result := vertical 
    else result := (pt2.Y - pt1.Y)/(pt2.X - pt1.X); 
end; 
//--------------------------------------------------------------------------- 

procedure GetLine(const pt1, pt2: TFloatPoint; out m, b: TFloat); 
begin 
    m := Slope(pt1, pt2); 
    if m = vertical then 
    b := pt1.X else 
    b := pt1.Y - m * pt1.X; 
end; 
//--------------------------------------------------------------------------- 

function GradientColor(const clr1, clr2: TColor32; fraction: TFloat): TColor32; 
begin 
    if fraction <= 0 then result := clr1 
    else if fraction >= 1 then result := clr2 
    else 
    begin 
    TColor32Entry(result).B := 
     trunc(TColor32Entry(clr2).B * fraction + TColor32Entry(clr1).B * (1-fraction)); 
    TColor32Entry(result).G := 
     trunc(TColor32Entry(clr2).G * fraction + TColor32Entry(clr1).G * (1-fraction)); 
    TColor32Entry(result).R := 
     trunc(TColor32Entry(clr2).R * fraction + TColor32Entry(clr1).R * (1-fraction)); 
    TColor32Entry(result).A := 
     trunc(TColor32Entry(clr2).A * fraction + TColor32Entry(clr1).A * (1-fraction)); 
    end; 
end; 
//--------------------------------------------------------------------------- 

function PointInTriangle(const pt, tr1, tr2, tr3: TFloatPoint): boolean; 
begin 
    result := false; 
    if ((((tr1.Y <= pt.Y) and (pt.Y < tr3.Y)) or 
    ((tr3.Y <= pt.Y) and (pt.Y < tr1.Y))) and 
    (pt.X < (tr3.X - tr1.X) * (pt.Y - tr1.Y)/
    (tr3.Y - tr1.Y) + tr1.X)) then result := not result; 
    if ((((tr2.Y <= pt.Y) and (pt.Y < tr1.Y)) or 
    ((tr1.Y <= pt.Y) and (pt.Y < tr2.Y))) and 
    (pt.X < (tr1.X - tr2.X) * (pt.Y - tr2.Y)/
    (tr1.Y - tr2.Y) + tr2.X)) then result := not result; 
    if ((((tr3.Y <= pt.Y) and (pt.Y < tr2.Y)) or 
    ((tr2.Y <= pt.Y) and (pt.Y < tr3.Y))) and 
    (pt.X < (tr2.X - tr3.X) * (pt.Y - tr3.Y)/
    (tr2.Y - tr3.Y) + tr3.X)) then result := not result; 
end; 
//--------------------------------------------------------------------------- 

function GetSegmentIndex(vertex: TFloatPoint; vertices: TArrayOfFloatPoint): integer; 
var 
    i, highI: integer; 
    prev: TFloatPoint; 
const 
    origin: TFloatPoint = (X: 0; Y: 0); 
begin 
    highI := high(vertices); 
    prev := vertices[highI]; 
    result := -1; 
    for i := 0 to highI do 
    begin 
    if PointInTriangle(vertex, origin, prev, vertices[i]) then 
    begin 
     result := i; 
     break; 
    end; 
    prev := vertices[i]; 
    end; 
end; 
//--------------------------------------------------------------------------- 

procedure RegularPolygonFill(bmp: TBitmap32; const origin: TPoint; 
    radius: TFloat; vertexCount: integer; InnerColor, OuterColor: TColor32); 
var 
    i,j,d,q: integer; 
    dist1,dist2: TFloat; 
    vert, intersetPt: TFloatPoint; 
    verts: TArrayOfFloatPoint; 
    edgeMs, edgeBs: TArrayOfFloat; 
    angle, angleDiff, m, b: TFloat; 
    sinAngle, cosAngle: extended; 
const 
    orig: TFloatPoint = (X: 0; Y: 0); 
begin 
    if vertexCount < 3 then exit; 
    setlength(verts, vertexCount); 
    setlength(edgeMs, vertexCount); //edge slopes (ie y = M*x +b) 
    setlength(edgeBs, vertexCount); //edge offsets (ie y = m*x +B) 
    angleDiff := pi *2/vertexCount; 
    angle := angleDiff; 
    vert.X := radius; //vert used here as prev vertex 
    vert.Y := 0; 
    for i := 0 to vertexCount -1 do 
    begin 
    SinCos(angle, sinAngle, cosAngle); 
    verts[i].X := cosAngle * radius; 
    verts[i].Y := sinAngle * radius; 
    GetLine(vert, verts[i], edgeMs[i], edgeBs[i]); 
    angle := angle + angleDiff; 
    vert := verts[i]; 
    end; 

    d := floor(radius); 
    for i := -d to d do 
    for j := -d to d do 
    begin 
     vert := FloatPoint(i,j); 
     GetLine(orig, vert, m, b); 
     q := GetSegmentIndex(vert, verts); 
     if q < 0 then continue; 
     //simultaneous equations to find intersection ... 
     //y = m * x + b; y = edgeMs[q]* x + edgeBs[q]; 
     //edgeMs[q]* x + edgeBs[q] = m * x + b; 
     //(edgeMs[q] - m) * x = b - edgeBs[q] 
     //x = (b - edgeBs[q])/(edgeMs[q] - m) 
     if m = vertical then 
     begin 
     intersetPt.X := b; 
     intersetPt.Y := edgeMs[q]* intersetPt.X + edgeBs[q]; 
     end 
     else if edgeMs[q] = vertical then 
     begin 
     intersetPt.X := edgeBs[q]; 
     intersetPt.Y := m* intersetPt.X + b; 
     end else 
     begin 
     intersetPt.X := (b - edgeBs[q])/(edgeMs[q] - m); 
     intersetPt.Y := m * intersetPt.X + b; 
     end; 

     //get distances from origin of vert and intersetPt ... 
     dist1 := sqrt(vert.X*vert.X + vert.Y*vert.Y); 
     dist2 := sqrt(intersetPt.X*intersetPt.X + intersetPt.Y*intersetPt.Y); 

     bmp.Pixel[i + origin.X, j + origin.Y] := 
     GradientColor(InnerColor, OuterColor, dist1/dist2); 
    end; 
end; 
+0

是的,我没有试图直接实施你的解决方案,但至少在理论上它应该工作。但是它涉及相当多的计算。我希望得到某种封闭的形式方程,更简单,更简洁。等式的输入将是笛卡尔坐标中的一个点,输出是距最近一侧的距离。 – 2012-08-05 12:50:57

+0

对于边数为偶数的多边形,只需计算多边形一半的像素,然后在中线上反射颜色,就可以显着加快这一速度。同样,对于具有4个边的倍数的多边形,只需计算多边形的四分之一像素,然后在水平和垂直中线上镜像像素颜色,就可以加快速度。 – 2012-08-05 12:57:36

+0

是的,它看起来完全正确。谢谢。你介意分享你的实现吗?我会非常感兴趣的看到一些实际的代码。 (语言无所谓) – 2012-08-05 13:22:55