使用 Python 将 WMF 图像卡通化
创建 Python 应用程序以通过服务器 API 卡通化 WMF 图像和照片
如何使用 Python 将 WMF 图像和照片卡通化
我们会自动对卡通图像做出反应,因为它们能够唤起怀旧感。在平面设计领域,卡通风格的图像是营销文章中常见的关键元素。这种卡通化效果涉及将照片肖像转换为手绘效果、调整亮度、转换为黑白、使用调色板以及合并各种编辑技术来制作复杂的视觉效果。一套图像过滤器,包括“AdjustBrightness”、“BinarizeFixed”、“Filter”、“ReplaceColor”和“ApplyMask”,使用户能够实现这些转换。这些过滤器可用于已下载的原始格式图像和照片。卡通风格的图像适用于各种网页的插图目的,为科学文章注入活力,并使内容对用户更具吸引力,从而增加网站的流量。要使用 WMF 图像生成卡通效果,我们将采用 Aspose.Imaging for Python via .NET API 是一个功能丰富、功能强大且易于使用的图像处理和转换 API,适用于 Python 平台。您可以使用系统命令中的以下命令安装它。
系统命令行
>> pip install aspose-imaging-python-net
通过 Python 对 WMF 进行卡通化的步骤
您需要 aspose-imaging-python-net 在您自己的环境中尝试以下工作流程。
- 使用 Image.Load 方法加载 WMF 文件 +卡通化图像;
- 以 Aspose.Imaging 支持的格式将压缩图像保存到光盘
系统要求
所有主要操作系统都支持 Python 的 Aspose.Imaging。只需确保您具有以下先决条件。
- 带有 .NET Core 运行时的 Microsoft Windows / Linux。
- Python 和 PyPi 包管理器。
Cartoonify WMF 图像 - Python
using Aspose.Imaging; | |
using Aspose.Imaging.FileFormats.Png; | |
using Aspose.Imaging.ImageFilters.FilterOptions; | |
using Aspose.Imaging.ImageOptions; | |
using Aspose.Imaging.Masking; | |
using Aspose.Imaging.Masking.Options; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
string templatesFolder = @"c:\Users\USER\Downloads"; | |
Cartoonify(); | |
void Cartoonify() | |
{ | |
FilterImages(image => | |
{ | |
using (var processedImage = new PngImage(image)) | |
{ | |
image.Resize(image.Width * 2, image.Height, ResizeType.LeftTopToLeftTop); | |
processedImage.Cartoonify(); | |
var gr = new Graphics(image); | |
gr.DrawImage(processedImage, processedImage.Width, 0); | |
gr.DrawLine(new Pen(Color.DarkRed, 3), processedImage.Width, 0, processedImage.Width, image.Height); | |
} | |
}, "cartoonify"); | |
} | |
string RasterizeVectorImage(string formatExt, string inputFile) | |
{ | |
string outputFile = Path.Combine(templatesFolder, $"rasterized.{formatExt}.png"); | |
using (var image = Image.Load(inputFile)) | |
{ | |
image.Save(outputFile, new PngOptions()); | |
} | |
return outputFile; | |
} | |
void FilterImages(Action<RasterImage> doFilter, string filterName) | |
{ | |
List<string> rasterFormats = new List<string>() { "jpg", "png", "bmp", "apng", "dicom", | |
"jp2", "j2k", "tga", "webp", "tif", "gif", "ico" }; | |
List<string> vectorFormats = new List<string>() { "svg", "otg", "odg", "eps", "wmf", "emf", "wmz", "emz", "cmx", "cdr" }; | |
List<string> allFormats = new List<string>(rasterFormats); | |
allFormats.AddRange(vectorFormats); | |
allFormats.ForEach( | |
formatExt => | |
{ | |
var inputFile = Path.Combine(templatesFolder, $"template.{formatExt}"); | |
bool isVectorFormat = vectorFormats.IndexOf(formatExt) > -1; | |
//Need to rasterize vector formats before background remove | |
if (isVectorFormat) | |
{ | |
inputFile = RasterizeVectorImage(formatExt, inputFile); | |
} | |
var outputFile = Path.Combine(templatesFolder, $"{filterName}_{formatExt}.png"); | |
Console.WriteLine($"Processing {formatExt}"); | |
using (var image = (RasterImage)Image.Load(inputFile)) | |
{ | |
doFilter(image); | |
//If image is multipage save each page to png to demonstrate results | |
if (image is IMultipageImage multiPage && multiPage.PageCount > 1) | |
{ | |
for (var pageIndex = 0; pageIndex < multiPage.PageCount; pageIndex++) | |
{ | |
string fileName = $"{filterName}_page{pageIndex}_{formatExt}.png"; | |
multiPage.Pages[pageIndex].Save(templatesFolder + fileName, new PngOptions()); | |
File.Delete(templatesFolder + fileName); | |
} | |
} | |
else | |
{ | |
image.Save(outputFile, new PngOptions()); | |
File.Delete(outputFile); | |
} | |
} | |
//Remove rasterized vector image | |
if (isVectorFormat) | |
{ | |
File.Delete(inputFile); | |
} | |
} | |
); | |
} | |
static class ImageFilterExtensions | |
{ | |
public static void Cartoonify(this RasterImage image) | |
{ | |
using var outlines = image.DetectOutlines(Color.Black); | |
image.AdjustBrightness(30); | |
image.Filter(image.Bounds, new MedianFilterOptions(7)); | |
var gr = new Graphics(image); | |
gr.DrawImage(outlines, Point.Empty); | |
} | |
public static RasterImage DetectOutlines(this RasterImage image, Color outlineColor) | |
{ | |
var outlines = new PngImage(image); | |
outlines | |
.GetDataContext() | |
.ApplyConvolutionFilter(ConvolutionFilterOptions.Blur) | |
.ApplyConvolutionFilter(ConvolutionFilterOptions.Outline) | |
.ApplyData(); | |
outlines.BinarizeFixed(30); | |
ImageMasking.ApplyMask(outlines, outlines, new MaskingOptions() { BackgroundReplacementColor = Color.Transparent }); | |
outlines.ReplaceColor(Color.FromArgb(255, 255, 255), 0, outlineColor); | |
outlines.ApplyConvolutionFilter(ConvolutionFilterOptions.Blur); | |
return outlines; | |
} | |
public static RasterImage ApplyOperationToRasterImage(this RasterImage image, Action<RasterImage> operation) | |
{ | |
if (image is IMultipageImage multipage) | |
{ | |
foreach (var page in multipage.Pages) | |
{ | |
operation.Invoke((RasterImage)page); | |
} | |
} | |
else | |
{ | |
operation.Invoke(image); | |
} | |
return image; | |
} | |
public static RasterImage ApplyFilter(this RasterImage image, FilterOptionsBase filterOptions) | |
{ | |
return image.ApplyOperationToRasterImage(img => | |
{ | |
img.Filter(img.Bounds, filterOptions); | |
}); | |
} | |
public static RasterImage ApplyConvolutionFilter(this RasterImage image, ConvolutionFilterOptions filterOptions) | |
{ | |
return image.ApplyOperationToRasterImage(img => | |
{ | |
var pixelsLoader = new ImagePixelsLoader(img.Bounds); | |
img.LoadPartialArgb32Pixels(img.Bounds, pixelsLoader); | |
var outBuffer = new PixelBuffer(img.Bounds, new int[img.Width * img.Height]); | |
ConvolutionFilter.DoFiltering(pixelsLoader.PixelsBuffer, outBuffer, filterOptions); | |
img.SaveArgb32Pixels(outBuffer.Rectangle, outBuffer.Pixels); | |
}); | |
} | |
public static IImageDataContext GetDataContext(this RasterImage image) | |
{ | |
IPixelBuffer GetImageBuffer(RasterImage img) | |
{ | |
var pixelsLoader = new ImagePixelsLoader(img.Bounds); | |
img.LoadPartialArgb32Pixels(img.Bounds, pixelsLoader); | |
return pixelsLoader.PixelsBuffer; | |
} | |
if (image is IMultipageImage multipage) | |
{ | |
return new MultipageDataContext( | |
multipage.Pages.Select(page => new ImageDataContext((RasterImage)page) | |
{ | |
Buffer = GetImageBuffer((RasterImage)page) | |
})); | |
} | |
return new ImageDataContext(image) | |
{ | |
Buffer = GetImageBuffer(image) | |
}; | |
} | |
public static IImageDataContext ApplyToDataContext(this IImageDataContext dataContext, | |
Func<IPixelBuffer, IPixelBuffer> processor) | |
{ | |
if (dataContext is MultipageDataContext multipage) | |
{ | |
foreach (var context in multipage) | |
{ | |
context.Buffer = processor.Invoke(context.Buffer); | |
} | |
} | |
if (dataContext is ImageDataContext imageDataContext) | |
{ | |
imageDataContext.Buffer = processor.Invoke(imageDataContext.Buffer); | |
} | |
return dataContext; | |
} | |
public static IImageDataContext ApplyConvolutionFilter(this IImageDataContext dataContext, | |
ConvolutionFilterOptions filterOptions) | |
{ | |
return dataContext.ApplyToDataContext(buffer => | |
{ | |
var outBuffer = new PixelBuffer(buffer.Rectangle, new int[buffer.Rectangle.Width * buffer.Rectangle.Height]); | |
ConvolutionFilter.DoFiltering(buffer, outBuffer, filterOptions); | |
return outBuffer; | |
}); | |
} | |
} | |
class ConvolutionFilter | |
{ | |
public static void DoFiltering( | |
IPixelBuffer inputBuffer, | |
IPixelBuffer outputBuffer, | |
ConvolutionFilterOptions options) | |
{ | |
var factor = options.Factor; | |
var bias = options.Bias; | |
var kernel = options.Kernel; | |
var filterWidth = kernel.GetLength(1); | |
var filterCenter = (filterWidth - 1) / 2; | |
int x, y; | |
int filterX, filterY, filterPx, filterPy, filterYPos, pixel; | |
double r, g, b, kernelValue; | |
int top = inputBuffer.Rectangle.Top; | |
int bottom = inputBuffer.Rectangle.Bottom; | |
int left = inputBuffer.Rectangle.Left; | |
int right = inputBuffer.Rectangle.Right; | |
for (y = top; y < bottom; y++) | |
{ | |
for (x = left; x < right; x++) | |
{ | |
r = 0; | |
g = 0; | |
b = 0; | |
for (filterY = -filterCenter; filterY <= filterCenter; filterY++) | |
{ | |
filterYPos = filterY + filterCenter; | |
filterPy = filterY + y; | |
if (filterPy >= top && filterPy < bottom) | |
{ | |
for (filterX = -filterCenter; filterX <= filterCenter; filterX++) | |
{ | |
filterPx = filterX + x; | |
if (filterPx >= left && filterPx < right) | |
{ | |
kernelValue = kernel[filterYPos, filterX + filterCenter]; | |
pixel = inputBuffer[filterPx, filterPy]; | |
r += ((pixel >> 16) & 0xFF) * kernelValue; | |
g += ((pixel >> 8) & 0xFF) * kernelValue; | |
b += (pixel & 0xFF) * kernelValue; | |
} | |
} | |
} | |
} | |
r = (factor * r) + bias; | |
g = (factor * g) + bias; | |
b = (factor * b) + bias; | |
r = r > 255 ? 255 : (r < 0 ? 0 : r); | |
g = g > 255 ? 255 : (g < 0 ? 0 : g); | |
b = b > 255 ? 255 : (b < 0 ? 0 : b); | |
outputBuffer[x, y] = ((inputBuffer[x, y] >> 24) << 24) | ((byte)r << 16) | ((byte)g << 8) | (byte)b; | |
} | |
} | |
} | |
} | |
class ConvolutionFilterOptions | |
{ | |
public double Factor { get; set; } = 1.0; | |
public int Bias { get; set; } = 0; | |
public double[,] Kernel { get; set; } | |
public static ConvolutionFilterOptions Blur | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } }, | |
Factor = 0.25 * 0.25 | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions Sharpen | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { 0, -1, 0 }, { -1, 5, -1 }, { 0, -1, 0 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions Emboss | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { -2, -1, 0 }, { -1, 1, 1 }, { 0, 1, 2 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions Outline | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { -1, -1, -1 }, { -1, 8, -1 }, { -1, -1, -1 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions BottomSobel | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions TopSobel | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions LeftSobel | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { 1, 0, -1 }, { 2, 0, -2 }, { 1, 0, -1 } } | |
}; | |
} | |
} | |
public static ConvolutionFilterOptions RightSobel | |
{ | |
get | |
{ | |
return new ConvolutionFilterOptions | |
{ | |
Kernel = new double[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } } | |
}; | |
} | |
} | |
} | |
interface IImageDataContext | |
{ | |
void ApplyData(); | |
} | |
class ImageDataContext : IImageDataContext | |
{ | |
public ImageDataContext(RasterImage image) | |
{ | |
this.Image = image; | |
} | |
public RasterImage Image { get; } | |
public IPixelBuffer Buffer { get; set; } | |
public void ApplyData() | |
{ | |
this.Buffer.SaveToImage(this.Image); | |
} | |
} | |
class MultipageDataContext : List<ImageDataContext>, IImageDataContext | |
{ | |
public MultipageDataContext(IEnumerable<ImageDataContext> enumerable) : base(enumerable) | |
{ | |
} | |
public void ApplyData() | |
{ | |
foreach (var context in this) | |
{ | |
context.ApplyData(); | |
} | |
} | |
} | |
class ImagePixelsLoader : IPartialArgb32PixelLoader | |
{ | |
public ImagePixelsLoader(Aspose.Imaging.Rectangle rectangle) | |
{ | |
this.PixelsBuffer = new CompositePixelBuffer(rectangle); | |
} | |
public CompositePixelBuffer PixelsBuffer { get; } | |
public void Process(Aspose.Imaging.Rectangle pixelsRectangle, int[] pixels, Point start, Point end) | |
{ | |
this.PixelsBuffer.AddPixels(pixelsRectangle, pixels); | |
} | |
} | |
interface IPixelBuffer | |
{ | |
Aspose.Imaging.Rectangle Rectangle { get; } | |
int this[int x, int y] | |
{ | |
get; | |
set; | |
} | |
void SaveToImage(RasterImage image); | |
} | |
class PixelBuffer : IPixelBuffer | |
{ | |
public PixelBuffer(Aspose.Imaging.Rectangle rectangle, int[] pixels) | |
{ | |
this.Rectangle = rectangle; | |
this.Pixels = pixels; | |
} | |
public Aspose.Imaging.Rectangle Rectangle { get; } | |
public int[] Pixels { get; } | |
public int this[int x, int y] | |
{ | |
get => this.Pixels[this.GetIndex(x, y)]; | |
set => this.Pixels[this.GetIndex(x, y)] = value; | |
} | |
public void SaveToImage(RasterImage image) | |
{ | |
image.SaveArgb32Pixels(this.Rectangle, this.Pixels); | |
} | |
public bool Contains(int x, int y) | |
{ | |
return this.Rectangle.Contains(x, y); | |
} | |
private int GetIndex(int x, int y) | |
{ | |
x -= this.Rectangle.Left; | |
y -= this.Rectangle.Top; | |
return x + y * this.Rectangle.Width; | |
} | |
} | |
class CompositePixelBuffer : IPixelBuffer | |
{ | |
private readonly List<PixelBuffer> _buffers = new List<PixelBuffer>(); | |
public CompositePixelBuffer(Aspose.Imaging.Rectangle rectangle) | |
{ | |
this.Rectangle = rectangle; | |
} | |
public Aspose.Imaging.Rectangle Rectangle { get; } | |
public int this[int x, int y] | |
{ | |
get => this.GetBuffer(x, y)[x, y]; | |
set => this.GetBuffer(x, y)[x, y] = value; | |
} | |
public void SaveToImage(RasterImage image) | |
{ | |
foreach (var pixelBuffer in this._buffers) | |
{ | |
pixelBuffer.SaveToImage(image); | |
} | |
} | |
public IEnumerable<PixelBuffer> Buffers => this._buffers; | |
public void AddPixels(Aspose.Imaging.Rectangle rectangle, int[] pixels) | |
{ | |
if (this.Rectangle.IntersectsWith(rectangle)) | |
{ | |
this._buffers.Add(new PixelBuffer(rectangle, pixels)); | |
} | |
} | |
private PixelBuffer GetBuffer(int x, int y) | |
{ | |
return this._buffers.First(b => b.Contains(x, y)); | |
} | |
} |
关于 Python API 的 Aspose.Imaging
Aspose.Imaging API 是一种图像处理解决方案,用于在应用程序中创建、修改、绘制或转换图像(照片)。它提供:跨平台的图像处理,包括但不限于各种图像格式之间的转换(包括统一的多页或多帧图像处理)、绘图等修改、使用图形基元、转换(调整大小、裁剪、翻转和旋转) 、二值化、灰度、调整)、高级图像处理功能(过滤、抖动、遮罩、去偏斜)和内存优化策略。它是一个独立的库,不依赖任何软件进行图像操作。可以在项目中使用原生 API 轻松添加高性能图像转换功能。这些是 100% 私有的本地 API,图像在您的服务器上处理。通过在线应用程序卡通化 WMF
通过访问我们的 Live Demos 网站 对 WMF 文档进行卡通化。 现场演示有以下好处
WMF 什么是 WMF 文件格式
带有 WMF 扩展名的文件代表 Microsoft Windows 元文件 (WMF),用于存储矢量以及位图格式的图像数据。更准确地说,WMF 属于与设备无关的图形文件格式的矢量文件格式类别。 Windows 图形设备接口 (GDI) 使用存储在 WMF 文件中的函数在屏幕上显示图像。后来发布了 WMF 的更增强版本,称为增强元文件 (EMF),使该格式的功能更加丰富。实际上,WMF 类似于 SVG。
阅读更多其他支持的卡通化格式
使用 Python,可以轻松卡通化不同的格式,包括。