c# - 调整用作水印的位图大小,结果显示黑色边框

标签 c# .net-core graphics gdi+ watermark

问题:
我想在图像上打印水印。图像的大小各不相同,因此有时水印太大,有时太小。为了解决这个问题,我计算图像的大小并调整水印的大小。但是,调整图像大小后,其边缘周围会出现黑色边框。

代码

我在使用 .NET Core3.1 的 Mac 上使用两个 NuGet 包来帮助绘制图像/位图。一个是 System.Drawing.Common,另一个是 runtime.osx.10.10x64.CoreCompat.System.Drawing,因为我在 macOS 上。

我用来调整水印大小的代码创建here :

Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));

我必须使用 /10 * 3,因为 Bitmap 构造函数不接受浮点值,因此我无法乘以 * 0.3

结果:

watermark before resizing watermark after resizing

watermark before            watermark after

最佳答案

要将一个图像叠加到另一个图像上,最好使用未缩放的图像,而不是预先根据所需大小生成新的位图。

▶ 这两个图像旨在混合,因此应执行其中一个图像(在本例中为水印图像)的缩放,同时使用 SourceOver 将要缩放的图像绘制在另一个图像上> 操作。
这样,内部 GDI+(这里是 GDI+ 副本)函数就可以正确计算混合过程。
这还可以防止副本显示使用 new Bitmap() 方法创建较小图像时生成的不完美的半透明像素(类似于暗光晕)。

▶ 另外,我们需要确保所有操作都在 32BitArgb 位图上执行。
最好创建目标图像的 32BitArgb 副本并在此副本上绘制水印。这样也能保证更好的结果。 GDI+ 功能在此类图像上效果更好。
在这里,CopyToArgb32() 方法负责这方面的工作,还将原始图像的 DPI 分辨率应用于副本。

▶ 此外,这会产生扭曲的图像(除非这是预期的结果):

Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));

应调整水印图像尺寸的大小,计算比例因子,该比例因子是所需分数(百分比或固定度量)或目标图像。

例如,占用的最大尺寸等于目标位图最小尺寸的三分之一。
换句话说,如果目标位图大小为 1500x600 px,水印位图将按比例缩放,最大高度为 200px:

float scale = (Math.Min(original.Width, original.Height) * .33f) / 
               Math.Min(watermark.Width, watermark.Height);
SizeF watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);

为了进一步改善混合效果,可以将水印设置得不那么不透明(或者更透明,如您所愿)。
这可以使用 ColorMatrix 简单地实现,如下所示:

How to apply a fade transition effect to Images


所有内容都组合在一个公开 Watermark([Bitmap], [Bitmap], [Imageformat]) 静态方法的类对象中。

在示例代码中,水印缩放至目标图像最大尺寸的 1/3 并居中(只是通用放置,因为未指定水印的位置):

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

public class BitmapOperations
{
    public static Bitmap Watermark(Bitmap watermark, Bitmap original, ImageFormat format)
    {
        var units = GraphicsUnit.Pixel;
        float scale = (Math.Max(original.Width, original.Height) * .33f) / 
                       Math.Max(watermark.Width, watermark.Height);
        var watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
        
        var watermarkBounds = CenterRectangleOnRectangle(
            new RectangleF(PointF.Empty, watermarkSize), original.GetBounds(ref units));
        var workImage = CopyToArgb32(original);
        // Using the SetOpacity() extension method described in the linked question
        // watermark = watermark.SetOpacity(.5f, 1.05f);

        using (var g = Graphics.FromImage(workImage)) {
            g.PixelOffsetMode = PixelOffsetMode.Half;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(watermark, watermarkBounds);

            return workImage;
        }
    }

    private static Bitmap CopyToArgb32(Bitmap source)
    {
        var bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
        bitmap.SetResolution(source.HorizontalResolution, source.VerticalResolution);
        using (var g = Graphics.FromImage(bitmap)) {
            g.DrawImage(source, new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                                new Rectangle(0, 0, bitmap.Width, bitmap.Height), GraphicsUnit.Pixel);
            g.Flush();
        }
        return bitmap;
    }

    private static RectangleF CenterRectangleOnRectangle(RectangleF source, RectangleF destination)
    {
        source.Location = new PointF((destination.Width - source.Width) / 2,
                                     (destination.Height - source.Height) / 2);
        return source;
    }
}

结果:

Image Watermark Blending

应用 50% 的不透明度级别和 Gamma 值的小修正:

Image Watermark Blending Opacity 50%

关于c# - 调整用作水印的位图大小,结果显示黑色边框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61974626/

相关文章:

c# - SQL 批量存储过程调用 C#

c# - Nancy 中的应用程序管道和模块管道之间的区别?

c# - Angular 6 - 单个表单组中的多个文件输入

docker - 没有端点在(URL)监听可以接受消息错误的端点

c# - 在 docker 中运行 c# worker 服务有意义吗?

c++ - 在透视图中旋转图像

DropDownList 样式的 C# ComboBox,如何设置文本?

Java 项目改变行为

java - 对于基于图 block 的横向卷轴游戏,如何让角色在跳跃后遇到方 block 时停止?

c# - 继承调用错误的 Equals() 方法