Skip to content
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*.cs]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea.

indent_style = space
indent_size = 4
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ git clone https://github.com/JimBobSquarePants/ImageSharp
- [x] YCbCr
- IPackedPixel\<TPacked\> representations of color models. Compatible with Microsoft XNA Game Studio and MonoGame.
- [x] Alpha8
- [x] Argb
- [x] Bgr565
- [x] Bgra444
- [x] Bgra565
Expand Down
309 changes: 309 additions & 0 deletions src/ImageSharp/Colors/PackedPixel/Argb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
using System;
using System.Numerics;

namespace ImageSharp
{
/// <summary>
/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255.
/// The color components are stored in alpha, red, green, and blue order.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct Argb : IPackedPixel<uint>, IEquatable<Argb>
{
const int BlueShift = 0;
const uint BlueMask = 0xFFFFFF00;
const int GreenShift = 8;
const uint GreenMask = 0xFFFF00FF;
const int RedShift = 16;
const uint RedMask = 0xFF00FFFF;
const int AlphaShift = 24;
const uint AlphaMask = 0x00FFFFFF;

/// <summary>
/// The maximum byte value.
/// </summary>
readonly static Vector4 MaxBytes = new Vector4(255);

/// <summary>
/// The half vector value.
/// </summary>
readonly static Vector4 Half = new Vector4(0.5F);

/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
public Argb(byte r, byte g, byte b, byte a = 255)
{
PackedValue = Pack(r, g, b, a);
}

/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
public Argb(float r, float g, float b, float a = 1)
{
PackedValue = Pack(r, g, b, a);
}

/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector containing the components for the packed vector.
/// </param>
public Argb(Vector3 vector)
{
PackedValue = Pack(ref vector);
}

/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector containing the components for the packed vector.
/// </param>
public Argb(Vector4 vector)
{
PackedValue = Pack(ref vector);
}

/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="packed">
/// The packed value.
/// </param>
public Argb(uint packed = 0)
{
PackedValue = packed;
}
/// <summary>
/// Gets or sets the red component.
/// </summary>
public byte R
{
get
{
return (byte)(PackedValue >> RedShift);
}

set
{
PackedValue = PackedValue & RedMask | (uint)value << RedShift;
}
}

/// <summary>
/// Gets or sets the green component.
/// </summary>
public byte G
{
get
{
return (byte)(PackedValue >> GreenShift);
}

set
{
PackedValue = PackedValue & GreenMask | (uint)value << GreenShift;
}
}

/// <summary>
/// Gets or sets the blue component.
/// </summary>
public byte B
{
get
{
return (byte)(PackedValue >> BlueShift);
}

set
{
PackedValue = PackedValue & BlueMask | (uint)value << BlueShift;
}
}

/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public byte A
{
get
{
return (byte)(PackedValue >> AlphaShift);
}

set
{
PackedValue = PackedValue & AlphaMask | (uint)value << AlphaShift;
}
}

/// <inheritdoc/>
public void PackFromVector4(Vector4 vector)
{
PackedValue = Pack(ref vector);
}

/// <inheritdoc/>
public Vector4 ToVector4()
{
return new Vector4(R, G, B, A) / MaxBytes;
}

/// <inheritdoc/>
public uint PackedValue { get; set; }

/// <inheritdoc/>
public void PackFromBytes(byte x, byte y, byte z, byte w)
{
PackedValue = Pack(x, y, z, w);
}

/// <summary>
/// Packs the four floats into a <see cref="uint"/>.
/// </summary>
/// <param name="x">The x-component</param>
/// <param name="y">The y-component</param>
/// <param name="z">The z-component</param>
/// <param name="w">The w-component</param>
/// <returns>The <see cref="uint"/></returns>
static uint Pack(float x, float y, float z, float w)
{
var value = new Vector4(x, y, z, w);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use var myself but I can clean this up after the merging the PR so don't worry about it.

Copy link
Contributor Author

@rokleM rokleM Dec 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, didn't see enough of the rest of the code to not use it (I do use var)!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not using var? 😄 The rule I use these days is: use var by default, unless the type cannot be easily inferred from reading the code. It makes the code more maintainable - for instance if I change from an IEnumerable to IList, etc, I don't need to update all the places, it kinda just works.

Copy link
Member

@JimBobSquarePants JimBobSquarePants Dec 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olivif What is the resultant type of x in this bit of code?

var x = (byte)1 + (byte)2;

I'm sure you know this already but it's an int due to implicit casting since bytes don't have an + operator. (This is the simplest example I could think of as an easy mistake to make using var)

I work with streams a lot and I've lost count of the amount of times that the wrong value has been passed to a BinaryWriter due to the use of var. That's always a nightmare to debug!

I know it's more common to use now but in certain critical codebases like the Roslyn compiler it's still banned.

I honestly believe that it's having a negative effect on modern programming. Devs aren't learning their types nor the framework properly as a result and are being careless, using var mostly because they don't know what the return type of a method should be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimBobSquarePants this is super interesting, I've actually never run into these type problems due to var, thanks for sharing 😄 . I understand the perils now, up until now it was just the maintainability argument for me, but now I see how it could go wrong.

I honestly believe that it's having a negative effect on modern programming. Devs aren't learning their types nor the framework properly as a result and are being careless, using var mostly because they don't know what the return type of a method should be.

I fully agree here. I was fortunate enough to write a lot of C++ before coming to C# but I realize that might not be the norm these days.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're better than I then! I tried C++ once..... Nope, too hard! 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha! Well, it was great at the time and definitely learnt a lot. Would I go back to unmanaged without a really good reason? Probably not 😄

return Pack(ref value);
}
/// <summary>
/// Packs the four floats into a <see cref="uint"/>.
/// </summary>
/// <param name="x">The x-component</param>
/// <param name="y">The y-component</param>
/// <param name="z">The z-component</param>
/// <param name="w">The w-component</param>
/// <returns>The <see cref="uint"/></returns>
static uint Pack(byte x, byte y, byte z, byte w)
{
return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift);
}

/// <summary>
/// Packs a <see cref="Vector3"/> into a uint.
/// </summary>
/// <param name="vector">The vector containing the values to pack.</param>
/// <returns>The <see cref="uint"/> containing the packed values.</returns>
static uint Pack(ref Vector3 vector)
{
var value = new Vector4(vector, 1);
return Pack(ref value);
}

/// <summary>
/// Packs a <see cref="Vector4"/> into a uint.
/// </summary>
/// <param name="vector">The vector containing the values to pack.</param>
/// <returns>The <see cref="uint"/> containing the packed values.</returns>
static uint Pack(ref Vector4 vector)
{
vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One);
vector *= MaxBytes;
vector += Half;
return (uint)(((byte)vector.X << RedShift)
| ((byte)vector.Y << GreenShift)
| ((byte)vector.Z << BlueShift)
| (byte)vector.W << AlphaShift);
}

/// <inheritdoc/>
public void ToBytes(byte[] bytes, int startIndex, ComponentOrder componentOrder)
{
switch(componentOrder) {
case ComponentOrder.ZYX:
bytes[startIndex] = B;
bytes[startIndex + 1] = G;
bytes[startIndex + 2] = R;
break;
case ComponentOrder.ZYXW:
bytes[startIndex] = B;
bytes[startIndex + 1] = G;
bytes[startIndex + 2] = R;
bytes[startIndex + 3] = A;
break;
case ComponentOrder.XYZ:
bytes[startIndex] = R;
bytes[startIndex + 1] = G;
bytes[startIndex + 2] = B;
break;
case ComponentOrder.XYZW:
bytes[startIndex] = R;
bytes[startIndex + 1] = G;
bytes[startIndex + 2] = B;
bytes[startIndex + 3] = A;
break;
default:
throw new NotSupportedException();
}
}

/// <summary>
/// Compares two <see cref="Color"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Color"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Color"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Argb left, Argb right)
{
return left.PackedValue == right.PackedValue;
}

/// <summary>
/// Compares two <see cref="Color"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Color"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Color"/> on the right side of the operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Argb left, Argb right)
{
return left.PackedValue != right.PackedValue;
}

/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is Argb && Equals((Argb)obj);
}

/// <inheritdoc/>
public bool Equals(Argb other)
{
return PackedValue == other.PackedValue;
}

/// <inheritdoc/>
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return PackedValue.GetHashCode();
}
}
}
45 changes: 45 additions & 0 deletions tests/ImageSharp.Tests/Colors/PackedPixelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,51 @@ public void Alpha8()
Assert.Equal(bgra, new byte[] { 0, 0, 0, 128 });
}

[Fact]
public void Argb()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

{
// Test the limits.
Assert.Equal((uint)0x0, new Argb(Vector4.Zero).PackedValue);
Assert.Equal(0xFFFFFFFF, new Argb(Vector4.One).PackedValue);

// Test ToVector4.
Assert.True(Equal(Vector4.One, new Argb(Vector4.One ).ToVector4()));
Assert.True(Equal(Vector4.Zero, new Argb(Vector4.Zero ).ToVector4()));
Assert.True(Equal(Vector4.UnitX, new Argb(Vector4.UnitX).ToVector4()));
Assert.True(Equal(Vector4.UnitY, new Argb(Vector4.UnitY).ToVector4()));
Assert.True(Equal(Vector4.UnitZ, new Argb(Vector4.UnitZ).ToVector4()));
Assert.True(Equal(Vector4.UnitW, new Argb(Vector4.UnitW).ToVector4()));

// Test clamping.
Assert.True(Equal(Vector4.Zero, new Argb(Vector4.One * -1234.0f).ToVector4()));
Assert.True(Equal(Vector4.One, new Argb(Vector4.One * +1234.0f).ToVector4()));

var x = +0.1f;
var y = -0.3f;
var z = +0.5f;
var w = -0.7f;
var argb = new Argb(x, y, z, w);
Assert.Equal(0x001a0080u, argb.PackedValue);

// Test ordering
byte[] rgb = new byte[3];
byte[] rgba = new byte[4];
byte[] bgr = new byte[3];
byte[] bgra = new byte[4];

argb.ToBytes(rgb, 0, ComponentOrder.XYZ);
Assert.Equal(rgb, new byte[] { 0x1a, 0, 0x80 });

argb.ToBytes(rgba, 0, ComponentOrder.XYZW);
Assert.Equal(rgba, new byte[] { 0x1a, 0, 0x80, 0 });

argb.ToBytes(bgr, 0, ComponentOrder.ZYX);
Assert.Equal(bgr, new byte[] { 0x80, 0, 0x1a });

argb.ToBytes(bgra, 0, ComponentOrder.ZYXW);
Assert.Equal(bgra, new byte[] { 0x80, 0, 0x1a, 0 });
}

[Fact]
public void Bgr565()
{
Expand Down