65.9K
CodeProject 正在变化。 阅读更多。
Home

一些很棒的图片效果

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (46投票s)

2015 年 5 月 29 日

CPOL

12分钟阅读

viewsIcon

48235

downloadIcon

4792

一个应用程序,可以为您的favorite图片创建有趣的效果。

1. 引言

图像可以被操纵以创建新图像的方法数不胜数;其中一些方法非常简单,但却能创造出看起来很酷且特别的新图像。许多软件应用程序都能创建这些酷炫的效果,理解这些效果背后的数学运算非常迷人。这些所谓的数学运算通常是简单的算术运算,易于理解和实现;它们的优点在于我们可以轻松地看到对图像产生的影响。

我们在互联网上可以找到许多这些数学公式;而另一些则隐藏在商业应用程序中的“商业秘密”里。在本文中,我们展示了一个程序,它能为您的favorite图片应用一些有趣的效果;同时我们也展示了创建这些效果的公式和简单的实现。为了使代码保持相对简单易懂,我们没有尝试任何优化来提高速度。我们希望您能享受这些酷炫的效果,并能理解相关的数学和代码。

2. 目标

本文档和软件应用程序的目标是:

  • 探索一些图像效果背后的数学,或者说是算术。
  • 使用户能够从以下选项中选择一种效果:
    1. 棕褐色:您的照片会随着时间的推移而逐渐褪色。
    2. 像素化:将图片中的块状区域平均化。
    3. 量化:五十度灰并入一种颜色。
    4. 波形:正弦波掠过。
    5. 曝光过度:将颜色混合在一起。
    6. 抛物线化:另一种颜色混合。
    7. X光:Wilhelm Roentgen(伦琴)前来打招呼,“你好,你好吗?”。
    8. 画布:织工在画布上编织您的图像。
    9. 抖动:一滴墨水思考——“我应该沉积吗,还是不沉积?”。
    10. 边缘:边缘之间的所有内容都会被冲刷掉。
    11. 浮雕:一位浅浮雕艺术家看到您的照片。
    12. 玻璃:有人给您送来一张磨砂玻璃板。
    13. 油画:虽然不是毕加索或梵高。
    14. 轮廓:另一种边缘的冲刷效果。
  • 更改这些效果的参数,并获得(接近)即时的预览。
  • 保存生成的图像。

如前所述,其中一些效果取自其他公共领域来源,我们对此表示衷心感谢;而其他效果是我们自己的“发现”——我们将适当地指出它们。

我们实现的一个重要方面是,我们希望所有的代码都是我们自己的。不使用第三方DLL。是的——我们的应用程序中有一个DLL,但其代码完全是我们自己的。

将代码保持独立,没有外部依赖(除了Windows API)是有原因的。我们看到如今初学者/学生倾向于使用图像处理包,尤其是那些只需一个命令的。这虽然有助于学习速度,但我们的印象是,这可能会在理解这类命令的实现方面留下空白。在此应用程序中,没有“命令”;我们直接通过代码操作每个R、G、B值。

3. 这些效果的算法

我们创建了一个PDF文件,其中包含所有公式以及简短的步骤描述。该文档在上面的下载列表中。它旨在作为方法的手册参考。我们试图使描述简单、易于遵循、快速阅读。我们还试图使描述简短且切中要点,以便在六页内涵盖我们所有的十四种效果。如果您在理解这些描述时遇到困难,请通过论坛与我们联系。

4. 软件使用

该应用程序的主屏幕如下所示

Cool Image Effects

正如作者之一在早期文章中所述,我们采用将输入图像缩放到600 x 600大小的策略,并在用户交互期间在此缩放图像上应用所有效果。一些真实的图像非常大;在这些大小的图像上应用效果可能会引起用户的不满,因为响应延迟。因此,我们将其缩放到600 x 600,然后用于预览。这在实时情况下表现得相当好。

我们设计的应用程序相当直观且自明。但是,为了完整起见,我们列出了使用步骤:

  1. 单击“打开图像”按钮打开图像。提供了一个默认图像供您试用。
  2. 使用下方的效果选择组合框选择任何效果。

    Cool Image Effects

  3. 更改任何参数,以在右侧窗格中查看效果。其中一些效果允许您通过单击下方的按钮来更改颜色。

    Cool Image Effects

  4. 通过右键单击右侧窗格来保存生成的图像。

    Cool Image Effects

5. 效果一览

在本节中,我们将在一张代表性图像上展示这些效果。下图显示了图像,这是一尊Kempegowda雕像,他是我们城市班加罗尔的创始人,距今已有400多年的历史。作者之一最近参观了市中心,并拍摄了这张照片。这张照片同时代表了我们城市新旧两方面的面貌,左侧是一座现代建筑。顺便说一句,这里的国际机场就是以他命名的。为了测试我们的文本酷炫效果,我们还在图像上添加了他的名字,用当地的卡纳达语和英语。

Kempegowda

现在,我们来看每种效果:

5a. 棕褐色

代码:SepiaAlgorithm.cs。计算棕褐色调的代码是

    // Target image
    for (el = 0; el < CurrentHeight; ++el) {
        for (k = 0; k < CurrentWidth; ++k) {
            index = el * CurrentWidth + k;
            r = Pixels8RedCurrent[index];
            g = Pixels8GreenCurrent[index];
            b = Pixels8BlueCurrent[index];
            // Grayscale
            intensity = 0.3 * r + 0.6 * g + 0.1 * b;

            // Red
            if (intensity > threshold)
                tone = 255.0;
            else
                tone = intensity + 255.0 - threshold;
            dRed = tone;

            // Similar code for green and blue components

            pixDoubleRed.Add(dRed);
            pixDoubleGreen.Add(dGreen);
            pixDoubleBlue.Add(dBlue);
        }
    }
}

这里,Pixels8RedCurrent[]是输入图像中的红色像素,而pixDoubleRed[]是创建的中间双精度图像中的相应像素。随后将它们缩放到0-255的范围,以获得棕褐色调的图像。

对于棕褐色滑块的两种不同设置,我们得到这些图像:

KempegowdaKempegowda

5b. 像素化

代码:PixelateAlgorithm.cs。

这里最重要的部分是设置块的几何形状。这是使用以下代码实现的。数学(或者说是算术)在可下载的PDF文件中进行了说明。

// Setting up the block widths and heights
void SetupBlockGeometry(List value) {
    var userBlockWidth = value.First(x => x.ParameterName == "BlockWidth");
    noHorizBlocks = userBlockWidth.Value;

    if (noHorizBlocks >= CurrentWidth)
        noHorizBlocks = CurrentWidth / 2;

    blockWidth = CurrentWidth / noHorizBlocks;
    blockWidths = Enumerable.Repeat(blockWidth, noHorizBlocks).ToList();

    // For the horizontal blocks
    var horizList = Enumerable.Range(0, noHorizBlocks - 1).ToList();
    // Shuffle the list.
    Random random = new Random(Environment.TickCount);
    shuffledHorizList = horizList.OrderBy(k => random.Next()).ToList();
    int diffH = CurrentWidth - noHorizBlocks * blockWidth;
    if (diffH > 0) {
        ++blockWidths[noHorizBlocks - 1];
        for (int i = 0; i < diffH - 1; ++i) {
            ++blockWidths[shuffledHorizList[i]];
        }
    }

    // Similar code for the vertical block size computation
}

之后是平均化块的颜色。 

对于两种不同的参数设置,我们得到这些图像:

KempegowdaKempegowda

5c. 量化

代码:QuantizeAlgorithm.cs。相关的代码提取如下:

for (int i = 0; i < Pixels8RedCurrent.Count; i++) {
    if (!bw) {
        Pixels8RedResult[i] = (byte)((Pixels8RedCurrent[i] / sizeOfStep) * sizeOfStep);
    } else {
        bGray = (byte)(0.3 * Pixels8RedCurrent[i] + 0.6 * Pixels8GreenCurrent[i]
            + 0.1 * Pixels8BlueCurrent[i]);
        Pixels8RedResult[i] = (byte)((bGray / sizeOfStep) * sizeOfStep);
        
        // Similar code for the green and blue components
    }
}

这段代码只是将像素值四舍五入到相应范围的最近中点。我们还提供了二值化功能。

 

KempegowdaKempegowda

5d. 波形

代码:WaveAlgorithm.cs

相关的代码提取在这里(同样,请参阅PDF中的数学部分)。这是一种图像扭曲的情况。

for (el = 0; el < CurrentHeight; ++el) {
    y = el;
    w2 = CurrentWidth * y;
    for (k = 0; k < CurrentWidth; ++k) {
        x = k;
        if (currentSelection == 1) {
            y = Convert.ToInt32(el + 20.0 * Math.Sin(2.0 * Math.PI * k / 128.0));
        } 

        // Source pixel
        w1 = CurrentWidth * y + x;
        r = Pixels8RedCurrent[w1];

        // Target pixel
        w1 = CurrentWidth * el + k;
        Pixels8RedResult[w1] = r;
 
        // Similar code for the green and blue components, and for the other cases
    }
}

KempegowdaKempegowda

5e. 曝光过度

代码:SolarizeAlgorithm.cs

相关代码是:

for (el = 0; el < CurrentHeight; ++el) {
    w2 = CurrentWidth * el;
    for (k = 0; k < CurrentWidth; ++k) {
        w1 = w2 + k;
        r = Pixels8RedCurrent[w1];

        if (r > threshold)
            r = (byte)(255 - r);

        Pixels8RedResult[w1] = r;
        // Similar code for green and blue components
    }
}

这涉及基于用户指定的阈值,对像素值进行某种程度的反转。

KempegowdaKempegowda

5f. 抛物线化

代码:ParabolaAlgorithm.cs

代码与曝光过度类似,但公式不同。请参阅PDF说明。

KempegowdaKempegowda

5g. X光

代码:XRayAlgorithm.cs

这是一个简单的算法,其代码是:

for (el = 0; el < CurrentHeight; ++el) {
    w2 = el * CurrentWidth;
    for (k = 0; k < CurrentWidth; ++k) {
        // Source Pixel
        index = w2 + k;
        r = Pixels8RedCurrent[index];
        g = Pixels8GreenCurrent[index];
        b = Pixels8BlueCurrent[index];
        dGray = 0.3 * r + 0.6 * g + 0.1 * b;
        
        // Code for clamping the pixel values ...
        // Compute the negative grayscale value
        dGray = 255 + factor - dGray;
        // factor is a magic number

        byteGray = (byte)dGray;
        SetBackgroundColour(colour, index, byteGray);
    }
}

KempegowdaKempegowda

5h. 画布

代码:CanvasAlgorithm.cs

如PDF文件所述,此算法及所有后续算法都是不可缩放算法。

此算法利用两张图像,其中一张是纹理图像。输入图像和纹理图像都经过合成,以创建新图像。

if (bVal < 128) {
    resultRed = (byte)((bVal * r) >> 7);
} else {
    resultRed = (byte)(255 - (((255 - bVal) * (255 - r)) >> 7));
}
// Similar code for green and blue components

这里,bVal来自纹理图像,而r、g和b来自输入图像。这是一种合成方法;还有许多其他可能性。

KempegowdaKempegowda

5i. 抖动

代码:DitherAlgorithm.cs

这利用了一个抖动矩阵,代码中提供了十个。

for (int j = 0; j < height; ++j) {
    start = width * j;
    for (int i = 0; i < width; ++i) {
        i1 = i + start;
        threshold = ditherMatrix[methodValue.Value - 1, j % n, i % n];
        if (PixGray[i1] > threshold) {
            SetBackgroundColour(colour, i1, value);
        } else {
            SetBackgroundColour(colour, i1, 0);
        }
    }
}

这里,pixGray是灰度值。

KempegowdaKempegowda

5j. 边缘

代码:EdgeAlgorithm.cs

这是一个相当复杂的算法,在PDF说明中已详细解释。相关代码提取如下:

ComputeGrayscaleImage();
ComputePixGrayAverage(CurrentHeight, CurrentWidth);
ComputeDxAndDyImages(CurrentHeight, CurrentWidth);
ComputeThetaAndMagnitudeImages(CurrentHeight, CurrentWidth);
ComputeMaxAndMinMagnitudeImage();
CreateFinalImage(CurrentHeight, CurrentWidth, threshold);

KempegowdaKempegowda

5k. 浮雕

代码:EmbossAlgorithm.cs

这使用了浮雕矩阵,并在PDF说明中进行了解释。提供了五个这样的浮雕矩阵。相关代码提取如下:

ComputeGrayscaleImage();
ComputeDoubleImage();
ComputeMaxAndMinDoubleImage();
CreateFinalImage(colour);

KempegowdaKempegowda

5l. 玻璃

代码:GlassAlgorithm.cs

在PDF说明中已详细解释。位置(i, j)处的像素被该像素附近用户指定的另一个像素替换。观察代码如何工作留给您作为练习。

KempegowdaKempegowda

5m. 油画

代码:OilPaintAlgorithm.cs

该算法的数学原理也在PDF说明中进行了解释。本质上,位置(i, j)处的像素被邻域内最常出现的像素值替换。

KempegowdaKempegowda

5n. 轮廓

代码:EdgeAlgorithm.cs

这是上面提到的边缘算法的一个特例。

KempegowdaKempegowda

6. MVVM范式的用法

该应用程序的初始版本基于WinForms,其中算法和表示方面紧密耦合;这从未发布过。我们决定将其转换为WPF,并重新设计为MVVM。除其他外,这使得算法部分可以重用。

这三个组件承担以下职责:

  1. 视图 (View): UI的外观,不包含应用程序逻辑。例如:CoolImageEffects.ImageProcessingView.
  2. 视图模型 (ViewModel): 准备模型的数据以供查看,并将工作流操作(逻辑)链接到UI。例如 CoolImageEffects.ImageProcessingViewModel
  3. 模型 (Model): 访问域实体和逻辑的网关。例如 ImageSource、Effects 和 Algorithm 选项。

通过这种MVVM范式,我们将表示逻辑与算法部分解耦。我们的应用程序中有两个项目:

  1. Algorithm:包含域实体和算法处理逻辑。它创建一个DLL。
  2. CoolImageEffects:包含视图和视图模型组件。这是可执行部分。

所有这些都封装在一个Visual Studio 2010解决方案中。

7. 关于代码的更多信息

如前所述,代码被组织成两个项目。

7a. Algorithm 项目

这是应用程序的核心。以下是此项目中文件的组织结构:

Cool Image Effects

最重要的文件集是:

  1. ImageData.cs:存储图像属性(宽度、高度)和图像缓冲区(R、G、B像素值)。此外,它还包含所有图像通用的加载、保存、设置背景色、转换为灰度等方法。
  2. AlgorithmBase.cs:作为所有算法的抽象基类。它声明了ApplyEffect方法,该方法在其子类中实现。
  3. 算法实现:实现实际的酷炫效果算法。

7b. CoolImageEffects 项目

这是应用程序部分。这里的组织结构是:

Cool Image Effects

这里重要的文件夹是:

  1. Converter:进行各种转换的地方。
  2. View:存放适合不同酷炫效果的视图。
  3. ViewModel:存放视图模型的地方。

由于重点更多地放在算法部分,我们不在此详细介绍。

7c. 添加您自己的效果

您可能会想出一个很棒的酷炫效果,并想尝试一下。该应用程序为您提供了此功能,前提是您精通C#编程。具体步骤如下:

  1. 将您的类添加到Algorithm项目。从AlgorithmBase派生它。如果您查看此类,您会发现两组变量:Pixels8nnnCurrentPixels8nnnResult,其中nnn是Red、Green或Blue之一。...Current变量是输入图像的像素缓冲区,而...Result变量是输出图像的像素缓冲区。这些是线性缓冲区。
  2. 在此类中实现ApplyEffectGetOptionsGetDisplayInfo方法。研究任何算法的代码,并相应地实现。
  3. 将您的算法名称添加到Effects枚举中。
  4. ImageProcessingAlgorithm类中实例化您的算法并将其添加到字典availableAlgorithms中。

由于我们提供了让您尝试酷炫效果的平台,我们很乐意您也能将您很棒的酷炫效果提供给广大社区。

7d. 您的下载

  1. 源代码(zip文件)。
  2. 可执行文件(zip文件)。
  3. PDF文档(zip文件)。

8. 值得关注的点

  1. 如果您浏览了下载中的PDF文档,您会注意到两种类型的效果——可缩放和不可缩放。可缩放意味着没有图像大小的依赖——具有相同“画面”但大小不同的两张图像会产生相同的结果。不可缩放效果则不是这样;用户在屏幕上看到的效果,在保存到原始图像时,可能会在视觉上注意到一个完全不同的效果。为了在一定程度上解决这个问题,我们提供了两种保存选项——保存原始图像和保存缩放后的图像。这些可以通过屏幕右侧图像窗格上的上下文菜单进行访问。
  2. “此应用程序没有bug”——我们认为我们已经足够成熟,会做出这样的声明。尽管我们使用不同类型和大小的图像进行了测试,但存在非零的可能性,您的图像和用户交互可能会将程序流程导向未经测试的区域。请对此提供反馈,我们将进行修正。
  3. “这是最优的MVVM实现”——这也是我们想做出的声明。是的——这是一个可行的解决方案,相对优化,并为用户提供近乎实时的效果。此外,代码的某些部分故意保持次优,以便初学者易于理解。例如,油画算法为3x3和5x5分别提供了两个独立的方法,显然是重复代码;虽然这些可以合并,但初学者可能难以理解。对我们来说,代码的可理解性,尤其是算法部分,非常重要。然而,这不应妨碍您对代码提出建议。

9. 结语

我们发现这些效果很酷,因此将应用程序命名为“酷炫效果”。我们相信您也会觉得它们足够酷。我们希望您会喜欢这个应用程序,并从中受益。把它展示给家里的孩子们,让他们玩玩,然后告诉我们他们的反馈。

在我们这个小型应用程序中封装的所有这些效果中,您最喜欢哪一种?为了使它对您更有吸引力,还可以为该效果添加哪些其他参数?您还想添加哪些其他效果?请也写下这方面的内容。

如前所述,这些效果的算法并非全新。然而,包装是新的。并且,将这些效果封装到易于理解的、可能对初学者来说不太困难的独立代码库中,或许是新的。该代码库还将允许您在不费太多麻烦的情况下添加新的酷炫效果。如果您在查看和保存图像后感到快乐和满意,请告诉我们。即使不满意,我们也感谢您的反馈。

10. 致谢

该应用程序是在业余时间开发的,当时我们在飞利浦印度公司工作;目的是在构建一个简单的图像处理应用程序的同时进行学习。我们衷心感谢我们部门的董事以及飞利浦知识产权和标准部门允许我们在网上发布本文。

历史

  • 版本 1.0: 2015年5月29日。
© . All rights reserved.