【问题标题】:Why does Graphics.DrawImage() change the colors (no resizing used)?为什么 Graphics.DrawImage() 会改变颜色(不使用调整大小)?
【发布时间】:2021-02-08 15:31:11
【问题描述】:

这是对另一个问题的简短测试,我尝试使用ImageAttributes.SetRemapTable() 替换图像中的颜色。我发现Graphics.DrawImage() 也改变了其他颜色,这不是颜色映射的一部分。

因此,我在 Graphics.DrawImage() 上创建了一个小测试。
我希望两个保存的图像是相同的:

  var bitmap = new Bitmap (1, 1, PixelFormat.Format32bppArgb);
  bitmap.SetPixel(0,0,Color.FromArgb(10,10,10,10));
  bitmap.Save (@"bitmap1.png", ImageFormat.Png);

  var bitmap2 = new Bitmap (1, 1, PixelFormat.Format32bppArgb);
  var graphics = Graphics.FromImage(bitmap2);
  graphics.DrawImage(bitmap, Point.Empty);
  bitmap2.Save (@"bitmap2.png", ImageFormat.Png);

bitmap1 中像素的 ARGB 为 10,10,10,10
bitmap2中像素的ARGB为10,0,0,0

为什么DrawImage() 没有像我预期的那样绘制?
我怎样才能得到预期的结果?

我不想创建自己的颜色替换方法;我希望 .net 的颜色映射能够正常工作。

编辑
我做了另一个测试,同样的 1 像素图像,但不是 ARGB 10,10,10,10,而是 ARGB 100,100,100,100。输出像素现在具有 ARGB 100,99,99,99
在我看来,这就像 GDI+ 或 .Net 中的错误,但绝对不是预期的计算输出。

而且,为了确保它不是由 1 个像素的宽度和高度引起的,我更改为

  var bitmap = new Bitmap (5, 5, PixelFormat.Format32bppArgb);
  bitmap.SetPixel(2,2,Color.FromArgb(100,100,100,100));

再一次,输出像素是 ARGB 100,99,99,99
现在看起来像是舍入错误,但上面的 10,0,0,0 绝对不是舍入错误。

编辑 2
现在看起来都像舍入错误:像素颜色的计算似乎是舍入的,例如在一个除法之后(可能是一个整数),所以舍入误差在另一个乘法中相乘。
此测试使用 16x16,RGB 为 20,100,255,Alpha 0(左上角)到 255(右下角)。
Beyond Compare中bitmap1(左)和bitmap2(右)的比较,结果在底部(结果图像的对比度增加),表明差异在低alpha时很高,在RGB中的差异在高alpha时向1​​或0减小:

示例值:

Pixel 0|0: Left = ARGB 0,20,100,255; Right = ARGB 0,0,0,0.  
Pixel 1|0: Left = ARGB 1,20,100,255; Right = ARGB 1,0,0,255.  
Pixel 2|0: Left = ARGB 2,20,100,255; Right = ARGB 2,0,127,255.  
Pixel 3|0: Left = ARGB 3,20,100,255; Right = ARGB 3,0,85,255. (85 = 1/3*255 => obviously division by alpha 3)
Pixel 3|0: Left = ARGB 4,20,100,255; Right = ARGB 4,0,127,255. (127 = 2/4*255 => obviously division by alpha 4)
Pixel 5|0: Left = ARGB 5,20,100,255; Right = ARGB 5,0,102,255. (102 = 2/5*255 => obviously division by alpha 5)
Pixel 15|0: Left = ARGB 15,20,100,255; Right = ARGB 15,17,102,255. (17 = 1/15*255 => obviously division by alpha 15)
Pixel 14|15: Left = ARGB 254,20,100,255; Right = ARGB 254,20,100,254.
Pixel 15|15: Left = ARGB 255,20,100,255; Right = ARGB 255,20,100,255.

是否有其他方法可以绘制部分透明的图像?

【问题讨论】:

  • 根据this post 的说法,使用 1x1 位图会将整个图像缩小到其边界,因此会弄乱所有 1 个像素。解决方案(使用带有半像素的 RectangleF)是否有助于实现您的目标将取决于细节..
  • @TaW:查看我的编辑
  • 并不是说它在这里改变了问题,你从来没有像人们需要的那样设置CompositingMode.SourceCopy..
  • @TaW:谢谢,这可能会解决另一个问题。但正如您已经指出的那样,它并不能解决这个问题。
  • “是否有其他方法可以绘制部分透明的图像?” -- 当然可以。您还尝试过哪些其他替代方案?到目前为止,您找到了哪些但还没有尝试过?您具体需要哪些帮助?显然,对于像素到像素的精确保真度,您可以LockBits() 并自己执行复制。或者:stackoverflow.com/a/4597984。我会指出,我已经尝试过您的场景,并且我发现当两个位图(原始和副本)被绘制到屏幕上时,屏幕上的实际像素值是相同的,尽管.. .

标签: c# colors graphics drawimage


【解决方案1】:

问题背后的目的是在图像中交换颜色。交换的颜色包括透明度(alpha 因此,最后,我创建了自己的方法:

/// ------------------------------------------------------------------
/// <summary>
/// Replace colors in a bitmap.<br/>
/// If <paramref name="i_expandOnAllAlpha"/> is true, the replacement
/// will not only be performed with the given colors, but it will be
/// expanded to colors that are built from the given colors with all
/// possible alpha values.
/// </summary>
/// <param name="i_bitmap">The bitmap in which colors will be replaced.</param>
/// <param name="i_oldAndNewColors">The collection of old and new colors.</param>
/// <param name="i_expandOnAllAlpha">A flag that specifies whether all alpha values (0-255) are included in the color replacement. E.g.: 1 given old color A[255]R[255]G[0]B[0] (red), 256 used old colors A[0-255]R[255]G[0]B[0].</param>
/// ------------------------------------------------------------------
public static Bitmap ReplaceColors (Bitmap i_bitmap,
                                    (Color OldColor, Color NewColor)[] i_oldAndNewColors,
                                    bool i_expandOnAllAlpha = false)
{
  if (i_bitmap == null)
    throw new ArgumentNullException (nameof (i_bitmap));

  var imageSize    = i_bitmap.Size;
  var pixelFormat  = i_bitmap.PixelFormat;
  int bitsPerPixel = Image.GetPixelFormatSize (pixelFormat);
  var bitmap       = (Bitmap)i_bitmap.Clone ();

  var colorMapping = new Dictionary<Color, Color> ();
  foreach (var oldAndNewColor in i_oldAndNewColors)
  {
    var oldColor = oldAndNewColor.OldColor;
    var newColor = oldAndNewColor.NewColor;

    if (i_expandOnAllAlpha)
    {
      for (int alpha = 0; alpha < 256; alpha++)
      {
        colorMapping.Add (Color.FromArgb (alpha, oldColor.R, oldColor.G, oldColor.B),
                          Color.FromArgb (alpha, newColor.R, newColor.G, newColor.B));
      }
    }
    else
    {
      colorMapping.Add (oldColor,newColor);
    }
  }

  BitmapData bitmapData = null;
  try
  {
    bitmapData = bitmap.LockBits (new Rectangle (new Point (), imageSize),
                                  ImageLockMode.ReadWrite,
                                  pixelFormat);
    var ptrBitmap = bitmapData.Scan0;

    int    rowByteCount    = CalcImageRowByteCount (new Size (bitmapData.Width, 0), bitsPerPixel);
    byte[] rowImageData    = new byte[rowByteCount];
    byte[] rowImageDataNew = new byte[rowByteCount];

    for (int rowIndex = 0; rowIndex < bitmapData.Height; rowIndex++)
    {
      var ptrRow = IntPtr.Add (ptrBitmap, rowIndex * bitmapData.Stride);
      Marshal.Copy (ptrRow, rowImageData, 0, rowByteCount);
      Array.Copy (rowImageData, rowImageDataNew, rowImageData.Length);

      int byteIndex = 0;
      switch (bitsPerPixel)
      {
      case 24:
        while (byteIndex < rowImageData.Length)
        {
          byte blueValue  = rowImageData[byteIndex];
          byte greenValue = rowImageData[byteIndex+1];
          byte redValue   = rowImageData[byteIndex+2];
          var  color      = Color.FromArgb (255, redValue, greenValue, blueValue);

          if (colorMapping.TryGetValue (color, out var newColor))
          {
            rowImageDataNew[byteIndex]     = newColor.B;
            rowImageDataNew[byteIndex + 1] = newColor.G;
            rowImageDataNew[byteIndex + 2] = newColor.R;
          }

          byteIndex += 3;
        }
        break;

      case 32:
        while (byteIndex < rowImageData.Length)
        {
          byte blueValue  = rowImageData[byteIndex];
          byte greenValue = rowImageData[byteIndex + 1];
          byte redValue   = rowImageData[byteIndex + 2];
          byte alphaValue = rowImageData[byteIndex + 3];
          var  color      = Color.FromArgb (alphaValue, redValue, greenValue, blueValue);

          if (colorMapping.TryGetValue (color, out var newColor))
          {
            rowImageDataNew[byteIndex]     = newColor.B;
            rowImageDataNew[byteIndex + 1] = newColor.G;
            rowImageDataNew[byteIndex + 2] = newColor.R;
            rowImageDataNew[byteIndex + 3] = newColor.A;
          }

          byteIndex += 4;
        }
        break;

      default:
        throw new NotImplementedException ();
      }

      Marshal.Copy (rowImageDataNew, 0, ptrRow, rowByteCount);
    }
  }
  finally
  {
    bitmap.UnlockBits (bitmapData);
  }

  return bitmap;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-07
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 2020-11-01
    • 2020-11-08
    • 1970-01-01
    相关资源
    最近更新 更多