Color.Equals seems totally useless

  • Thread starter Thread starter Ben Voigt [C++ MVP]
  • Start date Start date
B

Ben Voigt [C++ MVP]

I guess this must be a well-known issue that I just hit my head on.

Color.Equals doesn't apply the usual equality test.

For example:

using (g = Graphics.FromImage(bmp))
g.FillRectangle(Brushes.Black, new Rectangle(Point.Empty, bmp.Size));

One might expect that for any (x, y) inside the image bounds,

bmp.GetPixel(x, y) == Color.Black

But it's not.

GetPixel returns an unnamed black color, while Color.Black returns a named
black color, and while the value is the same (0xff000000) they compare
unequal!

Anyone know a valid reason for this insanity before I post a bug on Connect?
 
Ben,

From the documentation for the Equals method on the Color structure:

This structure only does comparisons with other Color structures. To compare
colors based solely on their ARGB values, you should use the ToArgb method.
This is because the Equals and Equality members determine equivalency using
more than just the ARGB value of the colors. For example, Black and
FromArgb(0,0,0) are not considered equal, since Black is a named color and
FromArgb(0,0,0) is not.

I'm not saying it is right, but it's not a bug, as it is documented to
work in this way.
 
Ben Voigt said:
I guess this must be a well-known issue that I just hit my head on.

Color.Equals doesn't apply the usual equality test.

For example:

using (g = Graphics.FromImage(bmp))
g.FillRectangle(Brushes.Black, new Rectangle(Point.Empty, bmp.Size));

One might expect that for any (x, y) inside the image bounds,

bmp.GetPixel(x, y) == Color.Black

But it's not.

GetPixel returns an unnamed black color, while Color.Black returns a named
black color, and while the value is the same (0xff000000) they compare
unequal!

Anyone know a valid reason for this insanity before I post a bug on Connect?

Well, I don't know that I'd say it's a real reason, but it's
documented:

<quote>
This structure only does comparisons with other Color structures. To
compare colors based solely on their ARGB values, you should use the
ToArgb method. This is because the Equals and Equality members
determine equivalency using more than just the ARGB value of the
colors. For example, Black and FromArgb(0,0,0) are not considered
equal, since Black is a named color and FromArgb(0,0,0) is not.
</quote>
 
Nicholas said:
Ben,

From the documentation for the Equals method on the Color
structure:
This structure only does comparisons with other Color structures. To
compare colors based solely on their ARGB values, you should use the
ToArgb method. This is because the Equals and Equality members
determine equivalency using more than just the ARGB value of the
colors. For example, Black and FromArgb(0,0,0) are not considered
equal, since Black is a named color and FromArgb(0,0,0) is not.

I'm not saying it is right, but it's not a bug, as it is
documented to work in this way.

Yeah I just didn't look hard enough...

No mention of that here:
http://msdn2.microsoft.com/en-us/library/system.drawing.color.equals(VS.85).aspx

And the second override just takes you to the general object.Equals docs.

But the first override gives the additional information you posted.
 
Nicholas said:
I'm not saying it is right, but it's not a bug, as it is documented to
work in this way.

IMHO, just because something is documented do not make it not a bug. e.g.
if Intel documented that floating point bug in their CPUs, then it is no
longer a bug?

If GetPixel returns Black then it should equal Color.Black, that's a bug
IMHO. If Msft really cared about 'named colors', then have Color.IsNamed
(color) or something similar.

Hilton
 
Hilton said:
IMHO, just because something is documented do not make it not a bug. e.g.
if Intel documented that floating point bug in their CPUs, then it is no
longer a bug?

If it had been documented before it had been found, I would call it a
flawed design decision rather than a bug in the implementation. To me,
"bug" = "not working as designed".
If GetPixel returns Black then it should equal Color.Black, that's a bug
IMHO. If Msft really cared about 'named colors', then have Color.IsNamed
(color) or something similar.

I suspect there's some reason we haven't thought of, to be honest.
 
Jon said:
If it had been documented before it had been found, I would call it a
flawed design decision rather than a bug in the implementation. To me,
"bug" = "not working as designed".

Many hardware and software products get shipped with known and (perhaps)
documented bugs. These are bugs, doesn't matter when some person decides to
write the documentation about it. OK, let's say that Microsoft release the
next C# compiler and it is designed to calculate "1+1=3" and they document
this behavior. Is that a bug? Of course it is even though it is "working
as designed". I don't think that it makes any difference where in the
development process the error occurs; i.e. in the research, design,
development, documentation, etc. Comparing two colors which are equal and
giving false sure sounds like a bug to me. Does black equal black? No.
Huh?

I suspect there's some reason we haven't thought of, to be honest.

My guess is that it was incorrectly written/changed to fix a bug somewhere
else in the framework. It worked, got checked in, and voila. In
pseudo-code, it should just be something along the lines of:

public bool Equal (Color color)
{
return (this.R == color.R) && (this.G == color.G) && (this.B == color.B);
}

Right?

I ran a test. The following code amazingly produced "False" and "0 0 0 255"
(both):

Color black = Color.FromArgb (0, 0, 0);
Color color = Color.Black;

MessageBox.Show ((black == color).ToString());

MessageBox.Show (string.Format ("{0} {1} {2} {3}", black.R, black.G,
black.B, black.A));
MessageBox.Show (string.Format ("{0} {1} {2} {3}", color.R, color.G,
color.B, color.A));


Hilton
 
Hilton said:
Many hardware and software products get shipped with known and (perhaps)
documented bugs. These are bugs, doesn't matter when some person decides to
write the documentation about it.

If it's documented as a bug, that's one thing. If it's documented as
intended behaviour, that's a different matter.
OK, let's say that Microsoft release the
next C# compiler and it is designed to calculate "1+1=3" and they document
this behavior. Is that a bug? Of course it is even though it is "working
as designed".

No, I wouldn't call that a bug - not in the software. It's a
flaw/problem/error - whatever you want to call it - in the design, but
not in the software itself.
I don't think that it makes any difference where in the
development process the error occurs; i.e. in the research, design,
development, documentation, etc. Comparing two colors which are equal and
giving false sure sounds like a bug to me. Does black equal black? No.
Huh?

The named colour "black" isn't equal to the unnamed colour which has
the same ARGB value. Should it be? Maybe. Maybe there's a good reason.

Either way, I still wouldn't call it a bug.
My guess is that it was incorrectly written/changed to fix a bug somewhere
else in the framework. It worked, got checked in, and voila.

Given the way it seems MS works, I suspect this really isn't the case.
In pseudo-code, it should just be something along the lines of:

public bool Equal (Color color)
{
return (this.R == color.R) && (this.G == color.G) && (this.B == color.B);
}

Right?

I ran a test. The following code amazingly produced "False" and "0 0 0 255"
(both):

Color black = Color.FromArgb (0, 0, 0);
Color color = Color.Black;

MessageBox.Show ((black == color).ToString());

MessageBox.Show (string.Format ("{0} {1} {2} {3}", black.R, black.G,
black.B, black.A));
MessageBox.Show (string.Format ("{0} {1} {2} {3}", color.R, color.G,
color.B, color.A));

I don't see why you're amazed when it's working exactly as it's
documented to.
 
My guess is that it was incorrectly written/changed to fix a bug
somewhere else in the framework. It worked, got checked in, and voila.

You're welcome to speculate as much as you want, of course. But
personally I find your guess lacking.

I find it much more likely that named colors are in fact treated
differently from unnamed colors in .NET/GDI/GDI+ and there's a much better
reason for them not comparing equal when the ARGB is equal than just that
someone forgot.

For example: the concept of named colors is not a lot different from the
older palette-based color systems. For various reasons, it often made
sense to compare two colors by their palette index rather than actually
looking up the actual color values for the color.

In the context of .NET, consider for example the issue of using a named
color in the context of two different color formats (whether varying only
by bits, or in the actual format, such as RGB vs HSV). Wouldn't it be
beneficial for the same named color to be treated as equal in those
contexts even if the way the color is ultimately used is different?

Conversely, what if the .NET implementors wanted the freedom to be able to
change the underlying implementation of the Color struct without affecting
how equality works and also without suffering the performance hit that
might be required to internally convert to some specific format just to do
an equality test?

This is all speculation as well, of course. But Microsoft has fixed
plenty of other bugs in .NET over the years. It's not like the Color
struct is brand new and it begs credulity to assert that this behavior is
simply a known bug that Microsoft has for whatever reason decided to
ignore. I don't see any rational reason to believe that they'd leave this
bug in when they do in fact fix a wide variety of other bugs as they
become known. It seems much more likely that there's some sort of
implementation detail or way that the Color struct is used that dictates
this particular behavior.

It'd be nice if Microsoft would fill us in on those details, but I don't
see that as mandatory. They do document the behavior, and they document
it as part of the design, not something that's a known bug. To assume
that it is in fact just an oversight, to fail to give Microsoft the
benefit of the doubt without justification for doing so, just doesn't make
any sense.

And finally:
[...]
I ran a test. The following code amazingly produced "False" and "0 0 0
255"
(both):

Given the information presented in this thread, why in the world are you
surprised? Why is it so amazing that the code does exactly what the
documentation says it will do?

Pete
 
Hilton said:
Many hardware and software products get shipped with known and (perhaps)
documented bugs. These are bugs, doesn't matter when some person decides
to write the documentation about it. OK, let's say that Microsoft release
the next C# compiler and it is designed to calculate "1+1=3" and they
document this behavior. Is that a bug? Of course it is even though it is
"working as designed". I don't think that it makes any difference where
in the development process the error occurs; i.e. in the research, design,
development, documentation, etc.

You're talking about 2 very different situations there and flip flopping
between them. The subtle but HUGE difference is that a design fault in known
before the code is written and a bug is discovered afterwards.
Comparing two colors which are equal and giving false sure sounds like a
bug to me. Does black equal black? No. Huh?

It sounds like an inconvenience to me.

Michael
 
Jon said:
If it's documented as a bug, that's one thing. If it's documented as
intended behaviour, that's a different matter.

So if C# returns 3 when I do "1+1" it is not a bug? If it does the wrong
thing, it is a bug. Anyway, we're kinda going around in circles.

No, I wouldn't call that a bug - not in the software. It's a
flaw/problem/error - whatever you want to call it - in the design, but
not in the software itself.


The named colour "black" isn't equal to the unnamed colour which has
the same ARGB value. Should it be? Maybe. Maybe there's a good reason.

Either way, I still wouldn't call it a bug.

Ask 1000 C# developers what "new Color (0, 0, 0).Equals (Color.Black)" would
return...

The MS docs say: "Tests whether the specified object is a Color structure
and is equivalent to this Color structure." Note *Equivalent*. Then
elsewhere, it says "equal" not "equivalent" - this is different.

Then the docs say: "This method compares more than the ARGB values of the
Color structures. It also does a comparison of some state flags. If you want
to compare just the ARGB values of two Color structures, use Color1.ToArgb()
== Color2.ToArgb()." Fine, it is documented, completely unintuitive (see
OP), but documented. So what are Color "state flags". If that is not
documented, then the docs are at best incomplete and ambiguous. Does MSFT
define Color state flags anywhere?

Hilton
 
So if C# returns 3 when I do "1+1" it is not a bug?

It's not a bug in the software if the software is specifically designed to
do that, no.
If it does the wrong thing, it is a bug.

If the software is specifically designed to do that, then it's not the
wrong thing for the software to do.
Anyway, we're kinda going around in circles.

Only because you are starting with a prejudiced view of what is a bug.

A Color structure is simply a piece of data. In this case, it happens to
represent an abstraction of something we know of in the real world. Some
such pieces of data follow the exact same rules that we follow in the real
world, but some do not. I submit to you that in this case, this data
structure does not.

Likewise, in Jon's reply to your "1+1=3" example, you are allowing your
prejudice of what that exact sequence of characters means in the real
world to affect your interpretation of what's going on in the computer.
While it's helpful for the compiler to be designed so that adding 1 to
itself results in 2, just as it does in the real world, some insane
compiler designer could in fact declare that his compiler will result in3
when that operation is performed.

He could do so for any variety of reasons, including that one or more of
those symbols don't actually mean in his compiler what they mean in the
real world, or simply that he wants to be arbitrary. But regardless, if
he writes the specification for the compiler stipulating that that's
what's supposed to happen, then if the compiler does exactly that, then
the compiler is bug-free (at least with respect to that feature).

You may rightfully say that the actual design of the compiler is flawed
(or even "buggy"). But the software itself is operating exactly as it was
designed, and thus is NOT flawed or buggy. It's not a bug for that
compiler, with that design, to make 3 the result of adding 1 and 1 (or
whatever operation might actually be represented by the sequence of
characters "1+1").
Ask 1000 C# developers what "new Color (0, 0, 0).Equals (Color.Black)"
would
return...

What it _would_ return? Only a developer who is familiar with the
structure and who knows the exact behavior of the Equals() method should
be answering that question, and they will tell you exactly what the
documentation says.

Change your question to "should return", and I'd agree that you might get
a wider variety of answers, including many (perhaps even a majority) who
agree with you on what it _should_ return.

But that wouldn't be relevant with respect to the question of whether the
structure's method has a bug in it or not. The only thing relevant there
is what Microsoft's .NET designers intended the method to do.
The MS docs say: "Tests whether the specified object is a Color structure
and is equivalent to this Color structure." Note *Equivalent*. Then
elsewhere, it says "equal" not "equivalent" - this is different.

You can nit-pick the docs all you want, the fact remains that they still
clearly state the behavior of the Equals() method.
Then the docs say: "This method compares more than the ARGB values of the
Color structures. It also does a comparison of some state flags. [...]"
So what are Color "state flags". If that is not
documented, then the docs are at best incomplete and ambiguous. Does
MSFT
define Color state flags anywhere?

They don't need to, since they aren't part of the API. That is, the
contract that the Color structure exposes to you.

I don't see how mentioning some aspect of the internal implementation of
the Color structure causes the documentation to be ambiguous.

I can more readily accept the characterization of "incomplete", but only
because it's practically impossible to provide documentation that is
"complete". In something as vast as .NET, there's always going to be
hidden corners where the documentation does not provide a literally
complete discussion of the API. But as long as these oversights don't
affect how you use the API, I consider them to be insignificant.

So, sure...the docs are incomplete, but in this case only in an
insignificant way. (There are lots of other places that the docs are
incomplete in a _significant_ way...why is this the issue you have decided
to chase down with such tenacity?)

And certainly in this case, you don't need to know what those flags are.
You don't really even need to know that they exist, though it's nice of
Microsoft to mention them so that perhaps you can at least have a little
bit more insight as to why the Color.Equals() method works the way it does.

Pete
 
Hilton said:
So if C# returns 3 when I do "1+1" it is not a bug? If it does the wrong
thing, it is a bug. Anyway, we're kinda going around in circles.

If a particular C# implementation were to return 1+1=3, that would be a
bug - because the specification says that it shouldn't.

To make this concrete, I don't like how overload resolution is applied
in certain cases - but it's not a bug in the C# compiler. It's a design
issue with the spec.
Ask 1000 C# developers what "new Color (0, 0, 0).Equals (Color.Black)" would
return...

Opinions don't dictate whether or not something is a bug. The designed
behaviour does. I could find plenty of other examples where most
developers would guess incorrectly at the answer to a question.
(Floating point is an obvious example where intuition is often wrong.)

Another concrete example:

string x = "mail";
string y = "MAIL";
bool b = (x.ToUpper() == y);

What's the value of b? I suspect if you ask 1000 C# developers, almost
all of them would say "true". Very few of them would say "It depends
whether or not the current culture is Turkish" which is the correct
answer.
The MS docs say: "Tests whether the specified object is a Color structure
and is equivalent to this Color structure." Note *Equivalent*. Then
elsewhere, it says "equal" not "equivalent" - this is different.

It doesn't have to be - they're defining the name as part of the
equivalence relation, that's all.
Then the docs say: "This method compares more than the ARGB values of the
Color structures. It also does a comparison of some state flags. If you want
to compare just the ARGB values of two Color structures, use Color1.ToArgb()
== Color2.ToArgb()." Fine, it is documented, completely unintuitive (see
OP), but documented. So what are Color "state flags". If that is not
documented, then the docs are at best incomplete and ambiguous. Does MSFT
define Color state flags anywhere?

Not that I've seen - and that's a fine and genuine beef. Report it on
connect.microsoft.com.
 
Jon said:
Another concrete example:

string x = "mail";
string y = "MAIL";
bool b = (x.ToUpper() == y);

What's the value of b? I suspect if you ask 1000 C# developers, almost
all of them would say "true". Very few of them would say "It depends
whether or not the current culture is Turkish" which is the correct
answer.

As one of the 1000, may I ask what x.ToUpper() is when the current culture is
Turkish? :)
 
As one of the 1000, may I ask what x.ToUpper() is when the current culture is
Turkish? :)

The I has an accent on it. Lower-casing has the same effect (so
y.ToLower()==x wouldn't help). I first ran into this in Java, with
exactly "mail" - I was trying to compare headers.

It's a particularly nasty bit of i18n.

Jon
 
Jon Skeet said:
The I has an accent on it.

The İ has a dot on it not an accent, just like i has.
Lower-casing has the same effect (so y.ToLower()==x wouldn't help).

Right, y.ToLower() is "maıl".
I first ran into this in Java, with exactly "mail" - I was trying to
compare headers.
It's a particularly nasty bit of i18n.

It's no worse than most other i18n.

NTFS has some trouble though. When creating or opening a file on an NTFS
partition, the upcasing table in the NTFS partition is supposed to be used
.... sometimes ... in order to determine if an equivalently named file
already exists. Sometimes I and ı don't compare as equal. Intuitively it
seems to me that it ought to work correctly if a Windows system had its
default system locale set to Turkish at the time of formatting the NTFS
partition, but intuitively it seems to me that it still likely has a bug.
 
Jon Skeet said:
No, I wouldn't call that a bug - not in the software. It's a
flaw/problem/error - whatever you want to call it - in the design, but not
in the software itself.

I think we agree that in principle it would be a bug in design but not a bug
in coding.

In practice I think it would yield quite a lot of bugs in the software.
Odds are that the C# compiler contains some + operators and depends on their
results to do a translation. Odds are that the I/O libraries contain some +
operators and depend on their results to write files. Fortunately kernel
mode drivers are in C without adding 1 to it.

In a millennium long long ago in a continent far far away, I changed the
addition table in a computer so it would calculate the result of 2 + 2 as
being 5. But my Fortran program didn't output 2 + 2 = 5, it just didn't
run, because the Fortran compiler and library had too much internal code
that depended on + producing correct results. (Demerit points to anyone who
says what computer this was.)
 
Norman Diamond said:
The İ has a dot on it not an accent, just like i has.

Apologies, yes.
Right, y.ToLower() is "maıl".


It's no worse than most other i18n.

I'd say it's worse because it's less expected than many other problems.
In my experience, people are more aware that translating something into
another language may well change how much space it will take up than
they are about this issue.

That's partly because (AFAIK) it only occurs in Turkish - whereas many
other i18n issues will become visible in many cultures.
 
Mr. Skeet, for shame: you took a posting which was encoded in UTF-8
displaying its contents correctly, and you followed up in ISO-8859-1
destroying exactly the contents which needed showing.

Anyway said:
That's partly because (AFAIK) it only occurs in Turkish - whereas many
other i18n issues will become visible in many cultures.

the problem becomes visible in every language *except* Turkish, so it should
be easier than most i18n issues ^_^


Norman Diamond said:
The İ has a dot on it not an accent, just like i has.

Apologies, yes.
Right, y.ToLower() is "maıl".


It's no worse than most other i18n.

I'd say it's worse because it's less expected than many other problems.
In my experience, people are more aware that translating something into
another language may well change how much space it will take up than
they are about this issue.

That's partly because (AFAIK) it only occurs in Turkish - whereas many
other i18n issues will become visible in many cultures.
 
Norman Diamond said:
Mr. Skeet, for shame: you took a posting which was encoded in UTF-8
displaying its contents correctly, and you followed up in ISO-8859-1
destroying exactly the contents which needed showing.

Sorry, my news client is lovely in many ways - but not great at
encodings :( The same will happen again with this one, I'm afraid. (It
understands various ISO-8859-? encodings, but not UTF-8.)
the problem becomes visible in every language *except* Turkish, so it should
be easier than most i18n issues ^_^

If you start off with the non-ASCII characters? Yes. I venture to claim
that doesn't happen as often as starting with the ASCII ones :)
 
Back
Top