【问题标题】:Mimicking this imaging effect模仿这种成像效果
【发布时间】:2015-03-30 08:53:00
【问题描述】:

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

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

当前代码示例如下。

有什么想法吗?

附言

这不是家庭作业。我正在寻找升级我在我的OSS项目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;
}
输入图像

电流输出

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

【问题讨论】:

  • @Jaydles 祝你好运。我可以轻松找到 1000 个更差的 cmets :)
  • @EZI,哦,我知道。哎呀,我写了一些更糟糕的,但我们都在努力成为牧羊人,当我们可以......:P
  • this question 中的一些可能的灵感。
  • 在我看来,您的问题的一个(可能很小)部分是绘图半径太小了..
  • 是啊,刚才太小了。不过,我可以很容易地增加它。如果没有正确的角度工作,我仍然会在颜色上重叠太多。

标签: c# graphics imageprocessor


【解决方案1】:

所以这是一个可行的解决方案。它不漂亮,速度不快(在我的笔记本电脑上是 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;
} 

旋转像素的附加代码如下。这可以在Rotating a point around another point找到。

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

    /// <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))
        };
    }

【讨论】:

  • 恕我直言,我认为您没有捕捉到 Photoshop 滤镜所具有的卡通风格。这看起来更像是 Floyd-Steinberg 抖动到
  • 我认为这与椭圆的比例一样多。如果放大图像,您可以看到重叠的角度 CMYK 图案和加色。无论如何,绝对不是 Floyd-Steinberg 算法。
  • 那部分没有问题,但是如果我将图像中的公共汽车轮廓与参考图像上的出租车或建筑物的轮廓进行比较,在我看来边缘是在参考图像中定义更多。
  • 是的......如果我减少间隔,我可以消除大部分,但我非常喜欢当前的输出。我想这是一个调整的问题。我认为 Photoshop实际上使用的是Ben-Day Dots而不是经典的半色调
猜你喜欢
  • 1970-01-01
  • 2014-02-19
  • 2023-03-28
  • 2012-03-19
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-10
相关资源
最近更新 更多