5

我问这个问题是因为另一个问题已经两岁了,没有准确回答。

我希望在 C#中复制本文中提到的 PhotoShop 效果。Adobe 称之为彩色半色调,我认为它看起来像是某种旋转的 CMYK 半色调。无论哪种方式,我都不知道我会怎么做。

当前代码示例如下。

有任何想法吗?

彩色半色调效果

附言

这不是家庭作业。我正在寻找升级我在我的 OSS 项目ImageProcessor中的漫画书效果。

ImageProcessor 漫画效果

进度更新。

所以这里有一些代码来展示我到目前为止所做的事情......

我可以相当容易且准确地在 CMYK 和 RGB 之间进行转换,以满足我的需要,并且还可以根据一系列点上每个颜色分量的强度打印出一系列带图案的椭圆。

我刚才遇到的问题是旋转每种颜色的图形对象,以便将点放置在代码中指定的角度。谁能给我一些指示如何去做?

public Image ProcessImage(ImageFactory factory)
{
    Bitmap newImage = null;
    Image image = factory.Image;

    try
    {
        int width = image.Width;
        int height = image.Height;

        // These need to be used.
        float cyanAngle = 105f;
        float magentaAngle = 75f;
        float yellowAngle = 90f;
        float keylineAngle = 15f;

        newImage = new Bitmap(width, height);
        newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

        using (Graphics graphics = Graphics.FromImage(newImage))
        {
            // Reduce the jagged edges.
            graphics.SmoothingMode = SmoothingMode.AntiAlias;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;

            graphics.Clear(Color.White);

            using (FastBitmap sourceBitmap = new FastBitmap(image))
            {
                for (int y = 0; y < height; y += 4)
                {
                    for (int x = 0; x < width; x += 4)
                    {
                        Color color = sourceBitmap.GetPixel(x, y);

                        if (color != Color.White)
                        {
                            CmykColor cmykColor = color;
                            float cyanBrushRadius = (cmykColor.C / 100) * 3;
                            graphics.FillEllipse(Brushes.Cyan, x, y, cyanBrushRadius, cyanBrushRadius);

                            float magentaBrushRadius = (cmykColor.M / 100) * 3;
                            graphics.FillEllipse(Brushes.Magenta, x, y, magentaBrushRadius, magentaBrushRadius);

                            float yellowBrushRadius = (cmykColor.Y / 100) * 3;
                            graphics.FillEllipse(Brushes.Yellow, x, y, yellowBrushRadius, yellowBrushRadius);

                            float blackBrushRadius = (cmykColor.K / 100) * 3;
                            graphics.FillEllipse(Brushes.Black, x, y, blackBrushRadius, blackBrushRadius);
                        }
                    }
                }
            }
        }

        image.Dispose();
        image = newImage;
    }
    catch (Exception ex)
    {
        if (newImage != null)
        {
            newImage.Dispose();
        }

        throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
    }

    return image;
}
输入图像

输入图像

电流输出

正如您所看到的,由于绘制的椭圆不是有角度的颜色输出是不正确的。

电流输出

4

1 回答 1

0

所以这是一个可行的解决方案。它不漂亮,速度不快(在我的笔记本电脑上是 2 秒),但输出很好。尽管我认为他们正在执行一些额外的工作,但它与 Photoshop 的输出并不完全匹配。

不同的测试图像上有时会出现轻微的莫尔条纹,但去网纹超出了当前问题的范围。

该代码执行以下步骤。

  1. 以给定的间隔循环遍历图像的像素
  2. 对于每个颜色分量,CMYK 在给定点绘制一个椭圆,该椭圆是通过将当前点旋转设定角度来计算的。这个椭圆的尺寸由每个点的每个颜色分量的级别决定。
  3. 通过循环像素点并在每个点添加 CMYK 颜色分量值来确定要绘制到图像的正确颜色来创建新图像。

输出图像

半色调图像

编码

public Image ProcessImage(ImageFactory factory)
{
    Bitmap cyan = null;
    Bitmap magenta = null;
    Bitmap yellow = null;
    Bitmap keyline = null;
    Bitmap newImage = null;
    Image image = factory.Image;

    try
    {
        int width = image.Width;
        int height = image.Height;

        // Angles taken from Wikipedia page.
        float cyanAngle = 15f;
        float magentaAngle = 75f;
        float yellowAngle = 0f;
        float keylineAngle = 45f;

        int diameter = 4;
        float multiplier = 4 * (float)Math.Sqrt(2);

        // Cyan color sampled from Wikipedia page.
        Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239));
        Brush magentaBrush = Brushes.Magenta;
        Brush yellowBrush = Brushes.Yellow;
        Brush keylineBrush;

        // Create our images.
        cyan = new Bitmap(width, height);
        magenta = new Bitmap(width, height);
        yellow = new Bitmap(width, height);
        keyline = new Bitmap(width, height);
        newImage = new Bitmap(width, height);

        // Ensure the correct resolution is set.
        cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution);
        magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution);
        yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution);
        keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution);
        newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

        // Check bounds against this.
        Rectangle rectangle = new Rectangle(0, 0, width, height);

        using (Graphics graphicsCyan = Graphics.FromImage(cyan))
        using (Graphics graphicsMagenta = Graphics.FromImage(magenta))
        using (Graphics graphicsYellow = Graphics.FromImage(yellow))
        using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
        {
            // Ensure cleared out.
            graphicsCyan.Clear(Color.Transparent);
            graphicsMagenta.Clear(Color.Transparent);
            graphicsYellow.Clear(Color.Transparent);
            graphicsKeyline.Clear(Color.Transparent);

            // This is too slow. The graphics object can't be called within a parallel 
            // loop so we have to do it old school. :(
            using (FastBitmap sourceBitmap = new FastBitmap(image))
            {
                for (int y = -height * 2; y < height * 2; y += diameter)
                {
                    for (int x = -width * 2; x < width * 2; x += diameter)
                    {
                        Color color;
                        CmykColor cmykColor;
                        float brushWidth;

                        // Cyan
                        Point rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), cyanAngle);
                        int angledX = rotatedPoint.X;
                        int angledY = rotatedPoint.Y;
                        if (rectangle.Contains(new Point(angledX, angledY)))
                        {
                            color = sourceBitmap.GetPixel(angledX, angledY);
                            cmykColor = color;
                            brushWidth = diameter * (cmykColor.C / 255f) * multiplier;
                            graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
                        }

                        // Magenta
                        rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), magentaAngle);
                        angledX = rotatedPoint.X;
                        angledY = rotatedPoint.Y;
                        if (rectangle.Contains(new Point(angledX, angledY)))
                        {
                            color = sourceBitmap.GetPixel(angledX, angledY);
                            cmykColor = color;
                            brushWidth = diameter * (cmykColor.M / 255f) * multiplier;
                            graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
                        }

                        // Yellow
                        rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), yellowAngle);
                        angledX = rotatedPoint.X;
                        angledY = rotatedPoint.Y;
                        if (rectangle.Contains(new Point(angledX, angledY)))
                        {
                            color = sourceBitmap.GetPixel(angledX, angledY);
                            cmykColor = color;
                            brushWidth = diameter * (cmykColor.Y / 255f) * multiplier;
                            graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
                        }

                        // Keyline 
                        rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), keylineAngle);
                        angledX = rotatedPoint.X;
                        angledY = rotatedPoint.Y;
                        if (rectangle.Contains(new Point(angledX, angledY)))
                        {
                            color = sourceBitmap.GetPixel(angledX, angledY);
                            cmykColor = color;
                            brushWidth = diameter * (cmykColor.K / 255f) * multiplier;

                            // Just using blck is too dark. 
                            keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K));
                            graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth);
                        }
                    }
                }
            }

            // Set our white background.
            using (Graphics graphics = Graphics.FromImage(newImage))
            {
                graphics.Clear(Color.White);
            }

            // Blend the colors now to mimic adaptive blending.
            using (FastBitmap cyanBitmap = new FastBitmap(cyan))
            using (FastBitmap magentaBitmap = new FastBitmap(magenta))
            using (FastBitmap yellowBitmap = new FastBitmap(yellow))
            using (FastBitmap keylineBitmap = new FastBitmap(keyline))
            using (FastBitmap destinationBitmap = new FastBitmap(newImage))
            {
                Parallel.For(
                    0,
                    height,
                    y =>
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // ReSharper disable AccessToDisposedClosure
                            Color cyanPixel = cyanBitmap.GetPixel(x, y);
                            Color magentaPixel = magentaBitmap.GetPixel(x, y);
                            Color yellowPixel = yellowBitmap.GetPixel(x, y);
                            Color keylinePixel = keylineBitmap.GetPixel(x, y);

                            CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel);
                            destinationBitmap.SetPixel(x, y, blended);
                            // ReSharper restore AccessToDisposedClosure
                        }
                    });
            }
        }

        cyan.Dispose();
        magenta.Dispose();
        yellow.Dispose();
        keyline.Dispose();
        image.Dispose();
        image = newImage;
    }
    catch (Exception ex)
    {
        if (cyan != null)
        {
            cyan.Dispose();
        }

        if (magenta != null)
        {
            magenta.Dispose();
        }

        if (yellow != null)
        {
            yellow.Dispose();
        }

        if (keyline != null)
        {
            keyline.Dispose();
        }

        if (newImage != null)
        {
            newImage.Dispose();
        }

        throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
    }

    return image;
} 

旋转像素的附加代码如下。这可以在围绕另一个点旋转一个点中找到

为简洁起见,我省略了颜色添加代码。

    /// <summary>
    /// Rotates one point around another
    /// <see href="https://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
    /// </summary>
    /// <param name="pointToRotate">The point to rotate.</param>
    /// <param name="centerPoint">The centre point of rotation.</param>
    /// <param name="angleInDegrees">The rotation angle in degrees.</param>
    /// <returns>Rotated point</returns>
    private static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
    {
        double angleInRadians = angleInDegrees * (Math.PI / 180);
        double cosTheta = Math.Cos(angleInRadians);
        double sinTheta = Math.Sin(angleInRadians);
        return new Point
        {
            X =
                (int)
                ((cosTheta * (pointToRotate.X - centerPoint.X)) -
                ((sinTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.X)),
            Y =
                (int)
                ((sinTheta * (pointToRotate.X - centerPoint.X)) +
                ((cosTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.Y))
        };
    }
于 2015-02-02T18:49:49.697 回答