Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BitmapData.Translate methods now properly convert premultiplied data #1985

Merged
merged 1 commit into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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