Skip to content

Commit

Permalink
Merge pull request #1985 from cwensley/curtis/fix-premultiplied-alpha
Browse files Browse the repository at this point in the history
BitmapData.Translate methods now properly convert premultiplied data
  • Loading branch information
cwensley authored Jul 12, 2021
2 parents 258d581 + 1cdf5c4 commit 3b6b4d3
Show file tree
Hide file tree
Showing 20 changed files with 525 additions and 159 deletions.
5 changes: 5 additions & 0 deletions src/Eto.Direct2D/2DConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public static s.Color ToDxColor(this Color color)
return new s.Color(color.R, color.G, color.B, color.A);
}

public static Color ToEto(this s.ColorBGRA value)
{
return Color.FromArgb(value.R, value.G, value.B, value.A);
}

public static Color ToEto(this s.Color4 value)
{
return new Color { A = value.Alpha, R = value.Red, G = value.Green, B = value.Blue };
Expand Down
33 changes: 29 additions & 4 deletions src/Eto.Direct2D/Drawing/BitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,43 @@ namespace Eto.Direct2D.Drawing
{
public class WicBitmapData : BaseBitmapData
{
public WicBitmapData(Image image, sw.BitmapLock bitmapLock, int bitsPerPixel)
: base(image, bitmapLock.Data.DataPointer, bitmapLock.Data.Pitch, bitsPerPixel, bitmapLock)
public WicBitmapData(Image image, sw.BitmapLock bitmapLock, int bitsPerPixel, bool premultipliedAlpha)
: base(image, bitmapLock.Data.DataPointer, bitmapLock.Data.Pitch, bitsPerPixel, bitmapLock, premultipliedAlpha)
{
}

public override int TranslateArgbToData(int argb)
{
if (PremultipliedAlpha)
{
var a = (uint)(byte)(argb >> 24);
var r = (uint)(byte)(argb >> 16);
var g = (uint)(byte)(argb >> 8);
var b = (uint)(byte)(argb);
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
return unchecked((int)((a << 24) | (r << 16) | (g << 8) | (b)));
}
return argb;
}

public override int TranslateDataToArgb(int bitmapData)
{
if (PremultipliedAlpha)
{
var a = (uint)(byte)(bitmapData >> 24);
var r = (uint)(byte)(bitmapData >> 16);
var g = (uint)(byte)(bitmapData >> 8);
var b = (uint)(byte)(bitmapData);
if (a > 0)
{
b = b * 255 / a;
g = g * 255 / a;
r = r * 255 / a;
}
return unchecked((int)((a << 24) | (r << 16) | (g << 8) | (b)));
}
return bitmapData;
}

Expand All @@ -48,12 +73,12 @@ public BitmapHandler(sw.Bitmap control)
{
Control = control;
}

public BitmapData Lock()
{
var data = Control.Lock(sw.BitmapLockFlags.Write);
var bpp = Control.PixelFormat == PixelFormat.Format24bppRgb.ToWic() ? 24 : 32;
return new WicBitmapData(Widget, data, bpp);
return new WicBitmapData(Widget, data, bpp, IsPremultiplied);
}

public void Unlock(BitmapData bitmapData)
Expand Down
35 changes: 26 additions & 9 deletions src/Eto.Direct2D/Drawing/ImageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ public class ImageHandler<TWidget> : WidgetHandler<sw.Bitmap, TWidget>, Image.IH
{
sd.Bitmap targetBitmap;
public sw.Bitmap[] Frames { get; protected set; }

public bool IsPremultiplied => Control.PixelFormat == s.WIC.PixelFormat.Format32bppPBGRA
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppPBGRA
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppPRGBAHalf
|| Control.PixelFormat == s.WIC.PixelFormat.Format128bppPRGBAFloat;

public bool HasAlpha => Control.PixelFormat == s.WIC.PixelFormat.Format32bppRGBA
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppRGBA
|| Control.PixelFormat == s.WIC.PixelFormat.Format32bppRGBA1010102
|| Control.PixelFormat == s.WIC.PixelFormat.Format32bppRGBA1010102XR
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppRGBAFixedPoint
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppRGBAHalf
|| Control.PixelFormat == s.WIC.PixelFormat.Format128bppRGBAFixedPoint
|| Control.PixelFormat == s.WIC.PixelFormat.Format128bppRGBAFloat
|| Control.PixelFormat == s.WIC.PixelFormat.Format32bppBGRA
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppBGRA
|| Control.PixelFormat == s.WIC.PixelFormat.Format16bppBGRA5551
|| Control.PixelFormat == s.WIC.PixelFormat.Format64bppBGRAFixedPoint;
public sd.Bitmap GetBitmap(sd.RenderTarget target)
{
if (targetBitmap == null || !Equals(targetBitmap.Tag, target.NativePointer))
Expand Down Expand Up @@ -151,11 +169,13 @@ public Color GetPixel(int x, int y)
{
var output = new uint[1];
Control.CopyPixels(new s.Rectangle(x, y, 1, 1), output);
var eto = new s.Color4(new s.ColorBGRA(output[0]).ToRgba()).ToEto();
if (Control.PixelFormat == sw.PixelFormat.Format24bppBGR)
return Color.FromRgb(eto.ToArgb());
else
return eto;
var eto = new s.ColorBGRA(output[0]).ToEto();
if (IsPremultiplied)
eto = Color.FromPremultipliedArgb(eto.ToArgb());
else if (!HasAlpha)
eto.A = 1;

return eto;
}
catch (s.SharpDXException ex)
{
Expand All @@ -164,10 +184,7 @@ public Color GetPixel(int x, int y)
}
}

public Size Size
{
get { return Control.Size.ToEto(); }
}
public Size Size => Control.Size.ToEto();

public void Create(Image image, int width, int height, ImageInterpolation interpolation)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Eto.Direct2D/Drawing/IndexedBitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void Create(int width, int height, int bitsPerPixel)
public BitmapData Lock()
{
var data = Control.Lock(sw.BitmapLockFlags.Write);
return new WicBitmapData(Widget, data, Widget.BitsPerPixel);
return new WicBitmapData(Widget, data, Widget.BitsPerPixel, false);
}

public void Unlock(BitmapData bitmapData)
Expand Down
84 changes: 64 additions & 20 deletions src/Eto.Gtk/Drawing/BitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace Eto.GtkSharp.Drawing
/// <license type="BSD-3">See LICENSE for full terms</license>
public class BitmapDataHandler : BaseBitmapData
{
public BitmapDataHandler(Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject)
: base(image, data, scanWidth, bitsPerPixel, controlObject)
public BitmapDataHandler(Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject, bool premultipliedAlpha)
: base(image, data, scanWidth, bitsPerPixel, controlObject, premultipliedAlpha)
{
}

Expand All @@ -29,6 +29,46 @@ public override int TranslateDataToArgb(int bitmapData)
}
}

/// <summary>
/// Bitmap data handler for surface data, which is always premultiplied
/// </summary>
/// <copyright>(c) 2012-2013 by Curtis Wensley</copyright>
/// <license type="BSD-3">See LICENSE for full terms</license>
public class SurfaceBitmapDataHandler : BaseBitmapData
{
public SurfaceBitmapDataHandler(Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject, bool premultipliedAlpha)
: base(image, data, scanWidth, bitsPerPixel, controlObject, premultipliedAlpha)
{
}

public override int TranslateArgbToData(int argb)
{
var a = (uint)(byte)(argb >> 24);
var r = (uint)(byte)(argb >> 16);
var g = (uint)(byte)(argb >> 8);
var b = (uint)(byte)(argb);
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
return unchecked((int)((a << 24) | (r << 16) | (g << 8) | (b)));
}

public override int TranslateDataToArgb(int bitmapData)
{
var a = (uint)(byte)(bitmapData >> 24);
var r = (uint)(byte)(bitmapData >> 16);
var g = (uint)(byte)(bitmapData >> 8);
var b = (uint)(byte)(bitmapData);
if (a > 0)
{
b = b * 255 / a;
g = g * 255 / a;
r = r * 255 / a;
}
return unchecked((int)((a << 24) | (r << 16) | (g << 8) | (b)));
}
}

/// <summary>
/// Bitmap handler.
/// </summary>
Expand Down Expand Up @@ -111,13 +151,16 @@ public void Create(Image image, int width, int height, ImageInterpolation interp

public BitmapData Lock()
{
EnsureData();
if (Surface != null)
{
return new SurfaceBitmapDataHandler(Widget, Surface.DataPtr, Surface.Stride, 32, null, true);
}
return InnerLock();
}

BitmapData InnerLock()
{
return new BitmapDataHandler(Widget, Control.Pixels, Control.Rowstride, Control.HasAlpha ? 32 : 24, null);
return new BitmapDataHandler(Widget, Control.Pixels, Control.Rowstride, Control.HasAlpha ? 32 : 24, null, false);
}

public void Unlock(BitmapData bitmapData)
Expand Down Expand Up @@ -290,21 +333,7 @@ public Color GetPixel(int x, int y)
{
using (var data = Lock())
{
unsafe
{
var srcrow = (byte*)data.Data;
srcrow += y * data.ScanWidth;
srcrow += x * data.BytesPerPixel;
if (data.BytesPerPixel == 4)
{
return Color.FromArgb(data.TranslateDataToArgb(*(int*)srcrow));
}
if (data.BytesPerPixel == 3)
{
return Color.FromRgb(data.TranslateDataToArgb(*(int*)srcrow));
}
throw new NotSupportedException();
}
return data.GetPixel(x, y);
}
}

Expand Down Expand Up @@ -351,6 +380,21 @@ public unsafe void FixupAlpha()
}
}

static int UnmultiplyAlpha(int argb)
{
var a = (uint)(byte)(argb >> 24);
var b = (uint)(byte)(argb >> 16);
var g = (uint)(byte)(argb >> 8);
var r = (uint)(byte)(argb);
if (a > 0)
{
b = b * 255 / a;
g = g * 255 / a;
r = r * 255 / a;
}
return unchecked((int)((a << 24) | (b << 16) | (g << 8) | (r)));
}

unsafe void EnsureData()
{
if (Surface == null)
Expand Down Expand Up @@ -388,7 +432,7 @@ unsafe void EnsureData()
var dest = (int*)destrow;
for (int x = 0; x < size.Width; x++)
{
*dest = bd.TranslateArgbToData(*src);
*dest = UnmultiplyAlpha(bd.TranslateArgbToData(*src));
src++;
dest++;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Eto.Gtk/Drawing/IndexedBitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Eto.GtkSharp.Drawing
public class IndexedBitmapDataHandler : BaseBitmapData
{
public IndexedBitmapDataHandler (Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject)
: base(image, data, scanWidth, bitsPerPixel, controlObject)
: base(image, data, scanWidth, bitsPerPixel, controlObject, false)
{
}

Expand Down
44 changes: 38 additions & 6 deletions src/Eto.Mac/Drawing/BitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ namespace Eto.Mac.Drawing
/// <license type="BSD-3">See LICENSE for full terms</license>
public class BitmapDataHandler : BaseBitmapData
{
public BitmapDataHandler(Bitmap bitmap, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject)
: base(bitmap, data, scanWidth, bitsPerPixel, controlObject)
public BitmapDataHandler(Bitmap bitmap, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject, bool isPremultiplied)
: base(bitmap, data, scanWidth, bitsPerPixel, controlObject, isPremultiplied)
{
}

Expand All @@ -64,12 +64,32 @@ public static int DataToArgb(int bitmapData)

public override int TranslateArgbToData(int argb)
{
return unchecked((int)(((uint)argb & 0xFF00FF00) | (((uint)argb & 0xFF) << 16) | (((uint)argb & 0xFF0000) >> 16)));
var a = (uint)(byte)(argb >> 24);
var r = (uint)(byte)(argb >> 16);
var g = (uint)(byte)(argb >> 8);
var b = (uint)(byte)(argb);
if (PremultipliedAlpha)
{
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
}
return unchecked((int)((a << 24) | (b << 16) | (g << 8) | (r)));
}

public override int TranslateDataToArgb(int bitmapData)
{
return unchecked((int)(((uint)bitmapData & 0xFF00FF00) | (((uint)bitmapData & 0xFF) << 16) | (((uint)bitmapData & 0xFF0000) >> 16)));
var a = (uint)(byte)(bitmapData >> 24);
var b = (uint)(byte)(bitmapData >> 16);
var g = (uint)(byte)(bitmapData >> 8);
var r = (uint)(byte)(bitmapData);
if (a > 0 && PremultipliedAlpha)
{
b = b * 255 / a;
g = g * 255 / a;
r = r * 255 / a;
}
return unchecked((int)((a << 24) | (r << 16) | (g << 8) | (b)));
}

public override bool Flipped { get { return false; } }
Expand Down Expand Up @@ -268,7 +288,15 @@ public Color GetPixel(int x, int y)
if (bmprep == null)
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Cannot get pixel data for this type of bitmap ({0})", rep?.GetType()));

return bmprep.ColorAt(x, y).ToEto(false);
// don't convert colorspace here otherwise we get incorrect data.. why?
var nscolor = bmprep.ColorAt(x, y);
if (nscolor.ComponentCount >= 3)
{
nscolor.GetRgba(out var red, out var green, out var blue, out var alpha);
return new Color(nscolor, (float)red, (float)green, (float)blue, (float)alpha);
}

return nscolor.ToEto();
}

protected override void Dispose(bool disposing)
Expand All @@ -287,7 +315,11 @@ protected override void Dispose(bool disposing)
public BitmapData Lock()
{
EnsureRep();
return bmprep == null ? null : new BitmapDataHandler(Widget, bmprep.BitmapData, (int)bmprep.BytesPerRow, (int)bmprep.BitsPerPixel, Control);
if (bmprep == null)
return null;

bool isPremultiplied = alpha && !bmprep.BitmapFormat.HasFlag(NSBitmapFormat.AlphaNonpremultiplied);
return new BitmapDataHandler(Widget, bmprep.BitmapData, (int)bmprep.BytesPerRow, (int)bmprep.BitsPerPixel, Control, isPremultiplied);
}

public void Unlock(BitmapData bitmapData)
Expand Down
2 changes: 1 addition & 1 deletion src/Eto.Mac/Drawing/IndexedBitmapHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace Eto.Mac.Drawing
public class IndexedBitmapDataHandler : BaseBitmapData
{
public IndexedBitmapDataHandler(Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject)
: base(image, data, scanWidth, bitsPerPixel, controlObject)
: base(image, data, scanWidth, bitsPerPixel, controlObject, false)
{
}

Expand Down
Loading

0 comments on commit 3b6b4d3

Please sign in to comment.