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

Promote PixelTypeInfo to Pixel #2601

Merged
merged 20 commits into from
Jan 8, 2024

Conversation

stefannikolei
Copy link
Contributor

Fixes #2534


internal static PixelTypeInfo Create<TPixel>(PixelAlphaRepresentation alpha)
internal static PixelTypeInfo Create<TPixel>(byte componentCount, PixelAlphaRepresentation pixelAlphaRepresentation)
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering whether we can remove this? I want to add the additional enum #2534 (comment) which means adding an additional property to the constructor.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe just add the additional parameter as having to duplicate Unsafe.SizeOf<TPixel>() * 8 is worse.

Copy link
Member

Choose a reason for hiding this comment

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

@stefannikolei I'm going to add this for you today.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah. To be honest.. totally forgot about that comment

@JimBobSquarePants
Copy link
Member

@stefannikolei I've made my changes, will need to do a sense check in the morning before merging as it's late now.

this.BitsPerPixel = bitsPerPixel;
this.AlphaRepresentation = alpha;
}
public byte ComponentCount { get; init; }
Copy link
Member

@antonfirsov antonfirsov Dec 11, 2023

Choose a reason for hiding this comment

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

We should consider changing this to int.

Suggested change
public byte ComponentCount { get; init; }
public int ComponentCount { get; init; }

Unless there is a reason to compress data for optimization (=the type is expected to be instantiated very frequently), it is generally recommended to prefer int over other integral types in .NET APIs. For example, IPEndPoint.Port is an int, while technically it could be an ushort. The reason is that this removes the need for conversion which is both expensive and complicated.

On the other hand, I noticed that we use non-int integrals in other descriptor types, eg. PngMetadata. I wish I noticed this earlier.

Copy link
Member

Choose a reason for hiding this comment

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

I’ve often wondered if there was a rule. For v4 we can definitely fix this.

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately it's not codified in the Framework Design Guidelines, but it exists for BCL APIs.

Copy link
Member

Choose a reason for hiding this comment

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

All good. I've fixed the property for this PR, we can look at others.

/// </summary>
public int BitsPerPixel { get; }
public PixelComponentPrecision? ComponentPrecision { get; init; }
Copy link
Member

@antonfirsov antonfirsov Dec 11, 2023

Choose a reason for hiding this comment

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

AFAIK there might be pixel types with asymmetric component precision. (I remember briefly touching this topic in an old discussion with Clinton.)

How would this model handle such pixel types?

Copy link
Member

Choose a reason for hiding this comment

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

It represents the maximum component precision. It’s in the description but perhaps including it in the type name or property name is better?

Copy link
Member

Choose a reason for hiding this comment

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

I looked at the usages of the property, and the only way we use it is the info.ComponentPrecision <= PixelComponentPrecision.Byte comparison, meaning that the only thing we need this for is to determine if the precision is below or above a certain treshold, and the exact precision information is practically unused. Unless we think there are use cases (proven by actual user user need) to observe the precision of the highest precision component, IMO we should try to avoid exposing such a property and explore alternative options to solve this.

Copy link
Member

Choose a reason for hiding this comment

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

My thought process is that users can implement custom performance optimized processors based upon this implementation. If they know the precision, they could operate over byte vs Vector4.

The use case that I've currently implemented fixes a bug we had also.

Rgba1010102 uses a uint as its packed value which is the same size as Rgba32 but we lose precision for the RGB components if we convert to it as we were since they require 10 bit precision.

Copy link
Member

Choose a reason for hiding this comment

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

I see that ComponentCount is a similar informative property. I didn't notice this fact in the #2534 discussion. The way I view this now is that we are attempting to add a Rich Pixel Type Descriptor feature. IMO there are a couple of important points we should consider before fully committing to it:

  • To really get this right, it might be better to expose a struct/class ComponentInfo and a property in PixelTypeInfo that enumerates the ComponentInfos, so each component can tell its precision separately instead of telling the maximum (for which I'm not sure if there is a use-case except the conversion implementation this PR is proposing). As far as I can recall that old discussion with Clinton, WIC has such a model but I may be wrong.
  • Note that this feature idea doesn't necessarily overlap with the Pixel <=> Color conversion. I don't see how can we implement efficient bulk conversion using these descriptor information only. In fact, if our goal is to solve the Pixel <=> Color conversion thing, it might be better to promote the conversion (& bulk conversion) methods to PixelOperations<T>.
  • Considering that this isn't super trivial to get right, I'm really curious if we have enough evidence (=enough users) proving that the Rich Pixel Type Descriptor feature is worth the headache.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's neat! I think you might have to be able to account for padding bits, so I'm not sure you can get away with not having a mask per channel. I'm trying to think of tricky examples... I've seen some BMPs that have gaps in the channel masks, but those were of the stress-test variety, not real world. You'll have real-world formats like B10G10R10, which is 32-bit with 2 bits padding, though.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure that's required for our use cases which is what precision can I safly store this pixel in.

We can tell the actual size of the pixel format from the info since the types themselves must handle padding. Unsafe.Sizeof<B10G10R10>() should be the same size Unsafe.Sizeof<Rgba1010102>() which is 4.

Theoretically we could add padding to the info. I'm assuming though that world is not completely mad and padding is always at the end?

Copy link
Contributor

@saucecontrol saucecontrol Dec 14, 2023

Choose a reason for hiding this comment

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

Right, I don't think you'll see a real-world case where BitsPerPixel - Sum(ChannelPrecision) doesn't give you the padding bits, which should always be at the end. Just trying to come up with reasons explicit masks might be beneficial, and weirdo pixel formats with interior padding are at least a possibility.

The other thing I guess that's useful in an explicit channel mask is that it also communicates the channel order. I'm not sure if WIC actually uses that information in that way, but I suppose it might. They don't expose channel order in any other way in their format definitions. It's just in the name and in the mask.

Edit: Never mind, there's no channel order conveyed in the mask. BGR and RGB have the same masks, so you can only know how to interpret them by knowing the name.

Copy link
Member

Choose a reason for hiding this comment

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

I think I'll go with real world for now. If someone wants something really weird, they can find a really weird library.

Copy link
Member

Choose a reason for hiding this comment

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

Type has been introduced as PixelComponentInfo

@@ -124,7 +125,9 @@ public static Color FromPixel<TPixel>(TPixel pixel)
{
return new((L16)(object)pixel);
}
else if (Unsafe.SizeOf<TPixel>() <= Unsafe.SizeOf<Rgba32>())

Copy link
Member

@antonfirsov antonfirsov Dec 11, 2023

Choose a reason for hiding this comment

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

While not critical, I would prefer an abstraction that can get rid of the Rgba64-L16 special cases above this line - by moving them to IPixel<T> / PixelOperations code.

Copy link
Member

Choose a reason for hiding this comment

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

Me also. In this case I think we should ditch the specialisation and box anything greater than byte. ColorFromPixel is not area for high performance operations.

On IPixel I would, if we can, drop all the various FromXX specialisation there also and ensure a good optimised bulk operations for all Rgba32 compatible pixel formats. That may not be possible though.

Copy link
Member

Choose a reason for hiding this comment

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

ColorFromPixel is not area for high performance operations.

Color.ToPixel already has a bulk variant, and I remember that we have found a use case for the inverse bulk operation: #2485 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Well remembered, but those are really short loops (max 256) in real terms. I'd hate to limit our flexibility and extensibility based on introducing a requirement there.

Ideally, I want to introduce as much agnostity as possible in our APIs.

Copy link
Member

@antonfirsov antonfirsov Dec 11, 2023

Choose a reason for hiding this comment

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

Boxing is very ugly, even if done <=256x typically, I would try to avoid it if there are alternatives.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we just back Color with Vector4? it already takes up the same amount of space and means we would be able to use bulk operations.

Copy link
Member

@antonfirsov antonfirsov Dec 13, 2023

Choose a reason for hiding this comment

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

The reason we introduced the ability to define (higher than Vector4) arbitrary-precison pixel types was this feature request: SixLabors/ImageSharp.Drawing#165

I like the fact that ImageSharp supports such "images". (At least for some use-cases)

Copy link
Member

Choose a reason for hiding this comment

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

Then use Vector4 as the default and box anything bigger. People expected double precision are in for a rough time with the library since almost everything is converted to use single precision at some point.

Copy link
Member

Choose a reason for hiding this comment

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

I've updated Color to use Vector4 to avoid boxing. I've also removed the implicit casting for random pixel formats in favour of explicit methods.

@JimBobSquarePants JimBobSquarePants added API area:pixelformats breaking Signifies a binary breaking change. labels Jan 2, 2024
Copy link
Member

@JimBobSquarePants JimBobSquarePants left a comment

Choose a reason for hiding this comment

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

I'm happy with this now. Would love a second or third opinion though.

Copy link
Contributor

@saucecontrol saucecontrol left a comment

Choose a reason for hiding this comment

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

Really nice improvement!

@JimBobSquarePants JimBobSquarePants merged commit 77cf848 into SixLabors:main Jan 8, 2024
5 checks passed
@stefannikolei stefannikolei deleted the sn/promote-pixeltype branch January 23, 2024 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API area:pixelformats breaking Signifies a binary breaking change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants