コード例 #1
0
ファイル: ColorUtil.cs プロジェクト: jonhartnett/IROM-Util
 /// <summary>
 /// Interpolates between the given colors using the given extended interpolation function.
 /// </summary>
 /// <param name="past">The past value.</param>
 /// <param name="bottom">The bottom value.</param>
 /// <param name="top">The top value.</param>
 /// <param name="future">The future value.</param>
 /// <param name="mu">The mu value.</param>
 /// <param name="func">The extended interpolation function to use.</param>
 /// <returns>The resulting color.</returns>
 public static ARGB Interpolate(ARGB past, ARGB bottom, ARGB top, ARGB future, double mu, ExtendedInterpFunction func)
 {
     return new ARGB(InterpByte(past.A, bottom.A, top.A, future.A, mu, func),
                     InterpByte(past.R, bottom.R, top.R, future.R, mu, func),
                     InterpByte(past.G, bottom.G, top.G, future.G, mu, func),
                     InterpByte(past.B, bottom.B, top.B, future.B, mu, func));
 }
コード例 #2
0
ファイル: RenderUtil.cs プロジェクト: jonhartnett/IROM-UI
        /// <summary>
        /// Renders a divider within the given rectangle. Uses the longer dimension as "with the grain".
        /// </summary>
        /// <param name="image">The image to render to.</param>
        /// <param name="boundary">The boundary to render in.</param>
        /// <param name="minColor">The minimum color.</param>
        /// <param name="midColor">The middle color.</param>
        /// <param name="maxColor">The maximum color.</param>
        /// <param name="interp">The interpolation function.</param>
        public static void RenderDivider(Image image, Rectangle boundary, ARGB minColor, ARGB midColor, ARGB maxColor, InterpFunction interp)
        {
            Rectangle area;
            if(boundary.Width >= boundary.Height)
            {
                area = boundary;
                int mid = (boundary.Min.Y + boundary.Max.Y) / 2;
                area.Max.Y = mid - 1;
                RenderXSide(image, false, boundary, minColor, midColor, interp);

                image.RenderSolid(new Rectangle{Min = new Point2D(mid, boundary.Min.Y), Max = new Point2D(mid, boundary.Max.Y)}, midColor);

                area = boundary;
                area.Min.Y = mid + 1;
                RenderXSide(image, true, boundary, maxColor, minColor, interp);
            }else
            {
                area = boundary;
                int mid = (boundary.Min.X + boundary.Max.X) / 2;
                area.Max.X = mid - 1;
                RenderYSide(image, false, boundary, minColor, midColor, interp);

                image.RenderSolid(new Rectangle{Min = new Point2D(boundary.Min.X, mid), Max = new Point2D(boundary.Max.X, mid)}, midColor);

                area = boundary;
                area.Min.X = mid + 1;
                RenderYSide(image, true, boundary, maxColor, minColor, interp);
            }
        }
コード例 #3
0
ファイル: RenderUtil.cs プロジェクト: jonhartnett/IROM-UI
 /// <summary>
 /// Renders a border between the given inner and outer rectangles.
 /// </summary>
 /// <param name="image">The render target.</param>
 /// <param name="outBoundary">The outer rectangle.</param>
 /// <param name="inBoundary">The inner rectangle.</param>
 /// <param name="outColor">The outer color.</param>
 /// <param name="inColor">The inner color.</param>
 /// <param name="roundEdges">True if the edges should be rounded.</param>
 /// <param name="interp">The interpolation function to use.</param>
 /// <param name="disableCenter">If true, disables filling of the center.</param>
 public static void RenderBorder(Image image, Rectangle outBoundary, Rectangle inBoundary, ARGB outColor, ARGB inColor, bool roundEdges, InterpFunction interp, bool disableCenter = false)
 {
     //render north west
     RenderCorner(image, false, false, new Rectangle{Min = outBoundary.Min, Max = inBoundary.Min - 1}, outColor, inColor, roundEdges, interp);
     //render north east
     RenderCorner(image, true, false, new Rectangle{Min = new Point2D(inBoundary.Max.X + 1, outBoundary.Min.Y), Max = new Point2D(outBoundary.Max.X, inBoundary.Min.Y - 1)}, outColor, inColor, roundEdges, interp);
     //render south west
     RenderCorner(image, false, true, new Rectangle{Min = new Point2D(outBoundary.Min.X, inBoundary.Max.Y + 1), Max = new Point2D(inBoundary.Min.X - 1, outBoundary.Max.Y)}, outColor, inColor, roundEdges, interp);
     //render south east
     RenderCorner(image, true, true, new Rectangle{Min = inBoundary.Max + 1, Max = outBoundary.Max}, outColor, inColor, roundEdges, interp);
     //render north
     RenderYSide(image, false, new Rectangle{X = inBoundary.X, Y = outBoundary.Min.Y   , Width = inBoundary.Width, Height = inBoundary.Min.Y - outBoundary.Min.Y}, outColor, inColor, interp);
     //render south
     RenderYSide(image, true, new Rectangle{X = inBoundary.X, Y = inBoundary.Max.Y + 1, Width = inBoundary.Width, Height = outBoundary.Max.Y - inBoundary.Max.Y}, outColor, inColor, interp);
     //render west
     RenderXSide(image, false, new Rectangle{X = outBoundary.Min.X,    Y = inBoundary.Y, Width = inBoundary.Min.X - outBoundary.Min.X, Height = inBoundary.Height}, outColor, inColor, interp);
     //render east
     RenderXSide(image, true, new Rectangle{X = inBoundary.Max.X + 1, Y = inBoundary.Y, Width = outBoundary.Max.X - inBoundary.Max.X, Height = inBoundary.Height}, outColor, inColor, interp);
     //render center
     if(!disableCenter) image.RenderSolid(inBoundary, inColor);
 }
コード例 #4
0
ファイル: NativeBuffer.cs プロジェクト: jonhartnett/IROM-Util
 private static extern IntPtr CreateDIBSection(IntPtr deviceContext, ref BITMAPINFO info, uint usage, out ARGB* pixels, IntPtr section, int offset);
コード例 #5
0
        /// <summary>
        /// Basic implementation of a constant src solid filling renderer.
        /// </summary>
        /// <param name="clip">The clipping rectangle.</param>
        /// <param name="scanner">The scans to render.</param>
        /// <param name="dest">The map to render to.</param>
        /// <param name="value">The constant value.</param>
        /// <param name="mode">The color blending mode to use.</param>
        /// <param name="isAA">True if anti-aliasing is enabled.</param>
        public static void SolidConstRender(Rectangle clip, Scanner scanner, DataMap<ARGB> dest, ARGB value, ColorMode mode = ColorMode.NORMAL, bool isAA = true)
        {
            //for constants, we can early return on blend and mask when alpha is 0
            if((mode == ColorMode.BLEND || mode == ColorMode.MASK) && value.A == 0) return;
            //mask at non-zero is equivalent to normal
            if(mode == ColorMode.MASK) mode = ColorMode.NORMAL;
            //blend at opaque is equivalent to normal
            if(mode == ColorMode.BLEND && value.A == 255) mode = ColorMode.NORMAL;

            IUnsafeMap uDest = dest as IUnsafeMap;
            if(uDest == null)
            {
                throw new InvalidOperationException("Destination image does not support advanced color rendering!");
            }
            byte* addr = uDest.BeginUnsafeOperation();
            int width = dest.Width;
            int stride = uDest.GetStride();

            for(int y = scanner.yMin; y <= scanner.yMax; y++)
            {
                double x1 = Max(scanner[y].min, clip.Min.X);
                double x2 = Min(scanner[y].max, clip.Max.X);
                //Anti-Aliasing variables
                //x1 vars are x min side
                //x2 vars are x max side
                //xb vars are y min side
                //xa vars are y max side
                double x1b = 0, x1a = 0;
                double x2b = 0, x2a = 0;
                int left;
                int right;
                if(isAA)
                {
                    bool hasMin = false, hasMax = false;
                    //if not at ymin or ymin is a clipped min (aka we have another "ghost" scan)
                    if(y > scanner.yMin || scanner.isYMinClipped)
                    {
                        x1b = Max(scanner[y - 1].min, clip.Min.X);
                        x2b = Min(scanner[y - 1].max, clip.Max.X);
                        x1b = (x1b + x1) / 2;
                        x2b = (x2b + x2) / 2;
                        hasMin = true;
                    }
                    //if not at ymax or ymax is a clipped max (aka we have another "ghost" scan)
                    if(y < scanner.yMax || scanner.isYMaxClipped)
                    {
                        x1a = Max(scanner[y + 1].min, clip.Min.X);
                        x2a = Min(scanner[y + 1].max, clip.Max.X);
                        x1a = (x1a + x1) / 2;
                        x2a = (x2a + x2) / 2;
                        hasMax = true;
                    }

                    if(!hasMin)
                    {
                        //if single line shape, just make everything literal
                        if(!hasMax)
                        {
                            x1b = x1a = x1;
                            x2b = x2a = x2;
                        }else//if at ymin, extrapolate max->current into current->min
                        {
                            x1b = x1 - (x1a - x1);
                            x2b = x2 - (x2a - x2);
                        }
                    }else//if at ymax, extrapolate min->current into current->max
                    if(!hasMax)
                    {
                        x1a = x1 + (x1 - x1b);
                        x2a = x2 + (x2 - x2b);
                    }

                    //if aa, round toward center
                    left = (int)Math.Ceiling(Max(x1b, x1a));
                    right = (int)Math.Floor(Min(x2b, x2a));
                }else
                {
                    //if not aa, round away from center to match default renderer
                    left = (int)Math.Floor(x1);
                    right = (int)Math.Ceiling(x2);
                }
                //if valid center
                if(left <= right)
                {
                    //solid fill with blend modes
                    byte* ptr = addr + (left + (y * width)) * stride;
                    byte* endPtr = ptr + (right - left + 1) * stride;
                    switch(mode)
                    {
                        case ColorMode.BLEND:
                        {
                            //precalc value
                            ARGB preValue = new ARGB{A = (byte)(255 - value.A),
                                                     R = (byte)((value.R * value.A) >> 8),
                                                     G = (byte)((value.G * value.A) >> 8),
                                                     B = (byte)((value.B * value.A) >> 8)};
                            while(ptr != endPtr)
                            {
                                //*((ARGB*)ptr) &= value;
                                //manually inline blending
                                ARGB color = *(ARGB*)ptr;
                                color = new ARGB{A = (byte)(value.A + color.A),
                                                 R = (byte)(preValue.R + ((color.R * preValue.A) >> 8)),
                                                 G = (byte)(preValue.G + ((color.G * preValue.A) >> 8)),
                                                 B = (byte)(preValue.B + ((color.B * preValue.A) >> 8))};
                                *(ARGB*)ptr = color;
                                ptr += stride;
                            }
                            break;
                        }
                        case ColorMode.NORMAL:
                        {
                            while(ptr != endPtr)
                            {
                                *((ARGB*)ptr) = value;
                                ptr += stride;
                            }
                            break;
                        }
                    }
                }
                //if aa, add smoothing to edges
                if(isAA)
                {
                    int center = (left + right) / 2;
                    if(Min(x1b, x1a) != left)
                        AAEdgeConst(addr, width, stride, x1b, x1a, y, value, true, clipMax: Min(center, clip.Max.X));
                    if(Max(x2b, x2a) != right)
                        AAEdgeConst(addr, width, stride, x2b + 1, x2a + 1, y, value, false, clipMin: Max(center + 1, clip.Min.X));
                }
            }

            uDest.EndUnsafeOperation();
        }
コード例 #6
0
        private static void AAEdgeCopy(byte* addr, int width, int stride, double x1, double x2, int y, byte* srcAddr, int srcWidth, int srcStride, Point2D offset, bool left, int clipMin = int.MinValue, int clipMax = int.MaxValue)
        {
            if(x1 > x2) Util.Swap(ref x1, ref x2);

            addr += (y * width * stride);
            srcAddr += ((y + offset.Y) * srcWidth * stride);
            double xmin;
            double xmax = x1;
            double ymin;
            double ymax = 1;
            double dydx = -1 / (x2 - x1);
            if(x2 == x1) dydx = 0;
            double alpha;
            do
            {
                xmin = xmax;
                xmax = Min((int)xmin + 1, x2);
                ymin = ymax;
                ymax = ymin + (xmax - xmin) * dydx;
                if(xmin < clipMin) continue;
                if(xmin > clipMax) break;
                alpha = (xmin - (int)xmin) * ymin;
                alpha += (ymax + ymin) * (xmax - xmin) / 2;
                ARGB color = *(ARGB*)(srcAddr + (((int)xmin + offset.X) * srcStride));
                *(ARGB*)(addr + ((int)xmin * stride)) &= new ARGB((byte)(color.A * (left ? (1 - alpha) : alpha)), color.RGB);
            }while(xmax != x2);
        }
コード例 #7
0
        private static void AAEdgeConst(byte* addr, int width, int stride, double x1, double x2, int y, ARGB value, bool left, int clipMin = int.MinValue, int clipMax = int.MaxValue)
        {
            if(x1 > x2) Util.Swap(ref x1, ref x2);

            addr += (y * width * stride);
            double xmin;
            double xmax = x1;
            double ymin;
            double ymax = 1;
            double dydx = -1 / (x2 - x1);
            if(x2 == x1) dydx = 0;
            double alpha;
            do
            {
                xmin = xmax;
                xmax = Min((int)xmin + 1, x2);
                ymin = ymax;
                ymax = ymin + (xmax - xmin) * dydx;
                if(xmin < clipMin) continue;
                if(xmin > clipMax) break;
                alpha = (xmin - (int)xmin) * ymin;
                alpha += (ymax + ymin) * (xmax - xmin) / 2;
                *(ARGB*)(addr + ((int)xmin * stride)) &= new ARGB((byte)(value.A * (left ? (1 - alpha) : alpha)), value.RGB);
            }while(xmax != x2);
        }
コード例 #8
0
        /// <summary>
        /// Basic implementation of a map src solid filling renderer.
        /// </summary>
        /// <param name="clip">The clipping rectangle.</param>
        /// <param name="scanner">The scans to render.</param>
        /// <param name="dest">The map to render to.</param>
        /// <param name="src">The source map.</param>
        /// <param name="offset">The offset of the source map.</param>
        /// <param name="mode">The color blending mode to use.</param>
        /// <param name="isAA">True if anti-aliasing is enabled.</param>
        public static void SolidCopyRender(Rectangle clip, Scanner scanner, DataMap<ARGB> dest, DataMap<ARGB> src, Point2D offset, ColorMode mode = ColorMode.NORMAL, bool isAA = true)
        {
            IUnsafeMap uDest = dest as IUnsafeMap;
            IUnsafeMap uSrc = src as IUnsafeMap;
            if(uDest == null)
            {
                throw new InvalidOperationException("Destination image does not support advanced color rendering!");
            }
            if(uSrc == null)
            {
                throw new InvalidOperationException("Source image does not support advanced color rendering!");
            }
            byte* addr = uDest.BeginUnsafeOperation();
            int width = dest.Width;
            int stride = uDest.GetStride();
            byte* srcAddr = uSrc.BeginUnsafeOperation();
            int srcWidth = src.Width;
            int srcStride = uSrc.GetStride();

            for(int y = scanner.yMin; y <= scanner.yMax; y++)
            {
                double x1 = Max(scanner[y].min, clip.Min.X);
                double x2 = Min(scanner[y].max, clip.Max.X);
                //Anti-Aliasing variables
                //x1 vars are x min side
                //x2 vars are x max side
                //xb vars are y min side
                //xa vars are y max side
                double x1b = 0, x1a = 0;
                double x2b = 0, x2a = 0;
                int left;
                int right;
                if(isAA)
                {
                    bool hasMin = false, hasMax = false;
                    //if not at ymin or ymin is a clipped min (aka we have another "ghost" scan)
                    if(y > scanner.yMin || scanner.isYMinClipped)
                    {
                        x1b = Max(scanner[y - 1].min, clip.Min.X);
                        x2b = Min(scanner[y - 1].max, clip.Max.X);
                        x1b = (x1b + x1) / 2;
                        x2b = (x2b + x2) / 2;
                        hasMin = true;
                    }
                    //if not at ymax or ymax is a clipped max (aka we have another "ghost" scan)
                    if(y < scanner.yMax || scanner.isYMaxClipped)
                    {
                        x1a = Max(scanner[y + 1].min, clip.Min.X);
                        x2a = Min(scanner[y + 1].max, clip.Max.X);
                        x1a = (x1a + x1) / 2;
                        x2a = (x2a + x2) / 2;
                        hasMax = true;
                    }

                    if(!hasMin)
                    {
                        //if single line shape, just make everything literal
                        if(!hasMax)
                        {
                            x1b = x1a = x1;
                            x2b = x2a = x2;
                        }else//if at ymin, extrapolate max->current into current->min
                        {
                            x1b = x1 - (x1a - x1);
                            x2b = x2 - (x2a - x2);
                        }
                    }else//if at ymax, extrapolate min->current into current->max
                    if(!hasMax)
                    {
                        x1a = x1 + (x1 - x1b);
                        x2a = x2 + (x2 - x2b);
                    }

                    //if aa, round toward center, otherwise round away to match default renderer
                    left = (int)Math.Ceiling(Max(x1b, x1a));
                    right = (int)Math.Floor(Min(x2b, x2a));
                }else
                {
                    //if not aa, round away to match default renderer
                    left = (int)Math.Floor(x1);
                    right = (int)Math.Ceiling(x2);
                }

                //if valid center
                if(left <= right)
                {
                    //solid fill with blend modes
                    byte* ptr = addr + (left + (y * width)) * stride;
                    byte* srcPtr = srcAddr + ((left + offset.X) + ((y + offset.Y) * srcWidth)) * srcStride;
                    byte* endPtr = ptr + (right - left + 1) * stride;
                    switch(mode)
                    {
                        case ColorMode.BLEND:
                        {
                            while(ptr != endPtr)
                            {
                                //*((ARGB*)ptr) &= *((ARGB*)srcPtr);
                                //manually inline blending
                                ARGB srcColor = *(ARGB*)srcPtr;
                                ARGB color = *(ARGB*)ptr;
                                int aComp = 255 - srcColor.A;
                                color = new ARGB{A = (byte)(srcColor.A + color.A),
                                                 R = (byte)(((srcColor.R * srcColor.A) + (color.R * aComp)) >> 8),
                                                 G = (byte)(((srcColor.G * srcColor.A) + (color.G * aComp)) >> 8),
                                                 B = (byte)(((srcColor.B * srcColor.A) + (color.B * aComp)) >> 8)};
                                *(ARGB*)ptr = color;
                                ptr += stride;
                                srcPtr += srcStride;
                            }
                            break;
                        }
                        case ColorMode.NORMAL:
                        {
                            while(ptr != endPtr)
                            {
                                *((ARGB*)ptr) = *((ARGB*)srcPtr);
                                ptr += stride;
                                srcPtr += srcStride;
                            }
                            break;
                        }
                        case ColorMode.MASK:
                        {
                            while(ptr != endPtr)
                            {
                                if((*((ARGB*)srcPtr)).A != 0)
                                    *((ARGB*)ptr) = *((ARGB*)srcPtr);
                                ptr += stride;
                                srcPtr += srcStride;
                            }
                            break;
                        }
                    }
                }
                //if aa, add smoothing to edges
                if(isAA)
                {
                    int center = (left + right) / 2;
                    if(Min(x1b, x1a) != left)
                        AAEdgeCopy(addr, width, stride, x1b, x1a, y, srcAddr, srcWidth, srcStride, offset, true, clipMax: Min(center, clip.Max.X));
                    if(Max(x2b, x2a) != right)
                        AAEdgeCopy(addr, width, stride, x2b + 1, x2a + 1, y, srcAddr, srcWidth, srcStride, offset, false, clipMin: Max(center + 1, clip.Min.X));
                }
            }

            uDest.EndUnsafeOperation();
            uSrc.EndUnsafeOperation();
        }
コード例 #9
0
ファイル: ARGB.cs プロジェクト: jonhartnett/IROM-Util
 /// <summary>
 /// Component-wise clips this <see cref="ARGB"/> to between min and max.
 /// </summary>
 /// <param name="min">The min color.</param>
 /// <param name="max">The max color.</param>
 /// <returns>The component-wise clipped color.</returns>
 public ARGB Clip(ARGB min, ARGB max)
 {
     return new ARGB(Util.Clip(A, min.A, max.A), Util.Clip(R, min.R, max.R), Util.Clip(G, min.G, max.G), Util.Clip(B, min.B, max.B));
 }
コード例 #10
0
ファイル: ARGB.cs プロジェクト: jonhartnett/IROM-Util
 public static bool TryParse(string str, out ARGB result)
 {
     result = default(ARGB);
     str = str.Trim();
     if(str.Length < "(0,0,0,0)".Length || str[0] != '(' || str[str.Length - 1] != ')') return false;
     //remove ( and )
     str = str.Substring(1, str.Length - 2);
     string[] parts = str.Split(',');
     if(parts.Length != 4) return false;
     if(!byte.TryParse(parts[0].Trim(), out result.A) ||
        !byte.TryParse(parts[1].Trim(), out result.R) ||
        !byte.TryParse(parts[2].Trim(), out result.G) ||
        !byte.TryParse(parts[3].Trim(), out result.B)) return false;
     return true;
 }
コード例 #11
0
ファイル: ARGB.cs プロジェクト: jonhartnett/IROM-Util
 public static string Serialize(ARGB value)
 {
     return string.Format("({0},{1},{2},{3})", value.A, value.R, value.G, value.B);
 }
コード例 #12
0
ファイル: ColorUtil.cs プロジェクト: jonhartnett/IROM-Util
 /// <summary>
 /// Interpolates between the given colors using the given interpolation function.
 /// </summary>
 /// <param name="bottom">The bottom value.</param>
 /// <param name="top">The top value.</param>
 /// <param name="mu">The mu value.</param>
 /// <param name="func">The interpolation function to use.</param>
 /// <returns>The resulting color.</returns>
 public static ARGB Interpolate(ARGB bottom, ARGB top, double mu, InterpFunction func)
 {
     return new ARGB(InterpByte(bottom.A, top.A, mu, func), InterpByte(bottom.R, top.R, mu, func), InterpByte(bottom.G, top.G, mu, func), InterpByte(bottom.B, top.B, mu, func));
 }
コード例 #13
0
ファイル: RenderUtil.cs プロジェクト: jonhartnett/IROM-UI
        /// <summary>
        /// Renders a corner gradient between the given colors. Includes outColor in the gradient but not inColor.
        /// </summary>
        /// <param name="image">The image to render to.</param>
        /// <param name="reverseX">True to reverse the gradient in the x direction.</param>
        /// <param name="reverseY">True to reverse the gradient in the y direction.</param>
        /// <param name="area">The area to fill.</param>
        /// <param name="outColor">The "outer" color.</param>
        /// <param name="inColor">The "inner" color.</param>
        /// <param name="roundEdges">True to round the corner, false for square.</param>
        /// <param name="interp">The interpolation function to use.</param>
        private static void RenderCorner(Image image, bool reverseX, bool reverseY, Rectangle area, ARGB outColor, ARGB inColor, bool roundEdges, InterpFunction interp)
        {
            Rectangle clip = ShapeUtil.Overlap((Rectangle)image.Size, image.GetClip(), area);
            if(clip.IsValid())
            {
                for(int i = clip.Min.X; i <= clip.Max.X; i++)
                {
                    for(int j = clip.Min.Y; j <= clip.Max.Y; j++)
                    {
                        double dx;
                        double dy;
                        if(reverseX) dx = i - area.Min.X;
                        else 		 dx = area.Max.X - i;
                        if(reverseY) dy = j - area.Min.Y;
                        else 		 dy = area.Max.Y - j;
                        dx++;
                        dx /= area.Width;
                        dy++;
                        dy /= area.Height;

                        double mu;
                        if(roundEdges)
                        {
                            mu = Math.Sqrt(dx * dx + dy * dy);
                        }else
                        {
                            mu = Math.Max(Math.Abs(dx), Math.Abs(dy));
                        }
                        mu = Util.Clip(mu, 0, 1);

                        image[i, j] = ColorUtil.Interpolate(inColor, outColor, mu, interp);
                    }
                }
            }
        }
コード例 #14
0
ファイル: RenderUtil.cs プロジェクト: jonhartnett/IROM-UI
 /// <summary>
 /// Renders a y-oriented gradient between the given colors. Includes outColor in the gradient but not inColor.
 /// </summary>
 /// <param name="image">The image to render to.</param>
 /// <param name="reverse">True to reverse the gradient.</param>
 /// <param name="area">The area to fill.</param>
 /// <param name="outColor">The "outer" color.</param>
 /// <param name="inColor">The "inner" color.</param>
 /// <param name="interp">The interpolation function to use.</param>
 private static void RenderYSide(Image image, bool reverse, Rectangle area, ARGB outColor, ARGB inColor, InterpFunction interp)
 {
     Rectangle clip = ShapeUtil.Overlap((Rectangle)image.Size, image.GetClip(), area);
     if(clip.IsValid())
     {
         for(int j = clip.Min.Y; j <= clip.Max.Y; j++)
         {
             double mu;
             if(reverse) mu = j - area.Min.Y;
             else 		mu = area.Max.Y - j;
             mu++;
             mu /= area.Height;
             mu = Util.Clip(mu, 0, 1);
             image.RenderSolid(new Rectangle{Min = new Point2D(clip.Min.X, j), Max = new Point2D(clip.Max.X, j)}, ColorUtil.Interpolate(inColor, outColor, mu, interp));
         }
     }
 }