w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.46k stars 657 forks source link

[css-images] @image rule for manipulating images #6807

Open LeaVerou opened 2 years ago

LeaVerou commented 2 years ago

There have been a lot of issues over the years about manipulating an existing image in some way before it's used by CSS. Some examples from a quick search:

There are also a bunch of features we defined and never got implementor interest, such as filter() or background-image-transform.

With my author hat on, I've also stumbled on use cases where I wanted to transform images, even in simple ways such as being able to override an image's intrinsic dimensions while setting background-size to a different size. Or just essentially setting object-fit: cover on a background-image so I could use background-size: <percentage>{2} without distortion.

What if we could address all of these in one fell swoop by creating a new at-rule to define images that CSS can then access through the image() function?

Something like:

@image --foo {
    src: url("foo.png");
    aspect-ratio: 1 / 1;
    width: 100vw;
    object-fit: cover;
    filter: blur(10px);
    opacity: .5;
    transform: rotate(5deg);
}

Which would then be used like:

background: image(--foo);
border-image: image(--foo); /* etc */

Since any <image> is allowed in src, this can be used to create variants of the same image:

@image --foo-larger {
    src: image(--foo);
    scale: 2;
}

The descriptors I envision being allowed in @image are:

The src descriptor could also support setting the source to a different image depending on resolution, as a nicer to read alternative of image-set().

Instead of:

background-image: image-set( "foo.png" 1x,
                             "foo-2x.png" 2x,
                             "foo-print.png" 600dpi );

it would be:

@image --foo {
    src: url("foo.png") 1x
           url("foo-2x.png") 2x,
          url("foo-print.png") 600dpi;
}

In fact, it would be nice if one could specify different descriptors depending on resolution, so that people could do things like:

@image --foo {
    src: url("foo.png") 1x;
}

@image --foo {
    src: url("foo.png") 2x;
    filter: blur(10px);
}

In the future, we may even want to add descriptors providing a fallback, color space, or other metadata about images.

The advantages of this syntax I see are:

background-image: filter(url("foo.png"), hue-rotate(135deg) opacity(.5));

with:

@image --foo {
    src: url("foo.png");
    filter: hue-rotate(135deg);
    opacity: .5;
}

/* ... */

background-image: image(--foo); 

The main downsides I see :

tabatkins commented 2 years ago

Big +1 here, I like this a lot. The fact that you can just use existing properties in easily-understood ways is a big plus here, both for authors and for us spec editors.

With functional syntaxes like filter() it's possible to use var() references trivially, and interpolation works out of the box. It's unclear if it's possible to have var() references resolve at the point of usage of the image, and interpolation may be trickier to define (but possible).

This is doable.

It has the same issues as @property wrt to global scope and shadow DOM (but that applies to most at-rules anyway and we likely need to fix it for all of them)

Already solved, yeah - this would define tree-scoped names.

booluw commented 2 years ago

Great stuff, but won't this increase the overall parse time of the styles. How is the performance metrics?

faceless2 commented 2 years ago

Re. different things depending on resolution, could you nest @media inside @image to do this? That would open all sorts of doors, without inventing another microsyntax.

LeaVerou commented 2 years ago

Re. different things depending on resolution, could you nest @media inside @image to do this? That would open all sorts of doors, without inventing another microsyntax.

Agreed, this would be great. Do note that the syntax above is not a new microsyntax, it's taken directly from image-set(). It is therefore an old microsyntax 😁

tabatkins commented 2 years ago

Yeah, using the image-set syntax and semantics is much better than trying to reproduce it thru media queries; MQs fundamentally can't handle resolution negotiation.

jsnkuhn commented 2 years ago

would clip-path inside @image function as a spriting syntax?

LeaVerou commented 2 years ago

would clip-path inside @image function as a spriting syntax?

I hadn't thought of that, but I don't see why not. Cool use case!

SebastianZ commented 2 years ago

@booluw wrote:

Great stuff, but won't this increase the overall parse time of the styles. How is the performance metrics?

As I understand it, the parse time doesn't increase much. It's the generation of the image that slows down its display a bit. I'm not an implementer but I assume the effect to be comparable to applying the existing properties to the element the image is used on.

What I mean by that is that a filter: blur(10px); in an @image rule would have a similar effect on the performance as a filter: blur(10px); on an element.

The difference is that it's just the image that's affected and not a whole element. And the generated image can be cached and reused in different places.


Thinking a bit more about this, I think it makes sense to split the manipulations from the image itself. So we'd have an @image-manipulation rule and a corresponding manipulate-image() function we put the images through.

Taking @LeaVerou's example this would then look like:

@image-manipulation --foo {
  aspect-ratio: 1 / 1;
  width: 100vw;
  object-fit: cover;
  filter: blur(10px);
  opacity: .5;
  transform: rotate(5deg);
}
background: manipulate-image(url("foo.png"), --foo);

This has the advantage, that authors can easily apply the same manipulations to different images and that existing logic for loading/generating the images can be reused.

So instead of

@image --foo {
  src: url("foo.png") 1x
       url("foo-2x.png") 2x,
       url("foo-print.png") 600dpi;
  ...
}

background-image: image(--foo);

for providing different image sources for the image manipulation you'd write

@image-manipulation --foo {
   ...
}

background-image: manipulate-image(
  image-set( "foo.png" 1x,
             "foo-2x.png" 2x,
             "foo-print.png" 600dpi
  ),
  --foo
);

Sebastian

LeaVerou commented 2 years ago

I love it! Yes, reusable image manipulations are far better than my original proposal (assuming we bikeshed the names).

jsnkuhn commented 2 years ago

If border-radius/corner-shape would be supported folks could do something like these over sized background pills: image

booluw commented 2 years ago

@SebastianZ reuseable image manipulations would actually help in performance since the logic would be cached.

jsnkuhn commented 2 years ago

I have a faint memory from back in the day that border-image was originally being implemented of folks taking issue with the fact that a 3x3 grid of a single image was necessary for border-image-source and that they wished there were a way to do it with just one instance of that image. @image would finally solve this by leveraging background-repeat and making an element size 3x that of the image to generate the 3x3 grid from the single image.

jsnkuhn commented 2 years ago

ran across a situation on walmart.com where I think @image might help:

image

.category-link{
  background-image: image(--foo);
  background-repeat: no-repeat;
  background-position: center center;
}

@image --foo {
  width: 90%;
  aspect-ratio: 1;
  background-color: #e2edfb;
  border-radius: 50%;
}
SebastianZ commented 2 years ago

@jsnkuhn Note that the idea behind this feature is to allow manipulating existing images. Your example obviously is meant to create one, which I believe is out of scope of this proposal.

Having said that, you could still achieve it with what was proposed earlier. With the proposed @image-manipulation rule (and yes, @LeaVerou, we should find a better name for it) this could be done with

@image-manipulation --circle {
  width: 190px;
  aspect-ratio: 1;
  clip-path: circle(closest-side);
}

.category-link {
  background-image: manipulate-image(image(#e2edfb), --circle);
  background-repeat: no-repeat;
  background-position: center center;
}

And regarding border-radius and corner-shape, I agree that they could be part of this as well but their effect can also be achieved by using clip-path: circle() as shown above or clip-path: path() for other shapes than circles.

Sebastian

SebastianZ commented 2 years ago

Btw. here are some name suggestions for rule and function name combinations:

Obviously some of them fit better than others, though I just wanted to get the ball rolling for that.

Sebastian

LeaVerou commented 2 years ago

You could easily create new images by starting something like src: image(transparent) or src: linear-gradient(...)

booluw commented 2 years ago

@SebastianZ ~I think the @image-customization / customize-image() rule/function is better. Descriptive~

@image() / @image is better, please. And if a user would use a gradient instead, should be defined inside of @image() since an image is been 'created/manipulated' with that rule.

booluw commented 2 years ago

ran across a situation on walmart.com where I think @image might help:

image

.category-link{
  background-image: image(--foo);
  background-repeat: no-repeat;
  background-position: center center;
}

@image --foo {
  width: 90%;
  aspect-ratio: 1;
  background-color: #e2edfb;
  border-radius: 50%;
}

Doesn't the width here causes a conflict with background-size property? Having two different widths might just leave room for buggy implementation.

SebastianZ commented 2 years ago

Just a recap, as it seems, people have forgotten about or have different understandings of the proposals:

@LeaVerou's initial idea was to create an image and manipulate it with this at-rule by defining the image source via the src descriptor and applying manipulations to it via the other descriptors. My idea was to just put the manipulation rules into the at-rule (without src descriptor), so you can reuse it for several images.

@LeaVerou wrote:

You could easily create new images by starting something like src: image(transparent) or src: linear-gradient(...)

@booluw wrote:

@image() / @image is better, please. And if a user would use a gradient instead, should be defined inside of @image() since an image is been 'created/manipulated' with that rule.

Did you think of the initial proposal or do you expect the src descriptor to be optional? If the latter, this would merge both proposals, i.e. allow to define a one-off manipulation when the src descriptor is provided or to reuse the manipulation by skipping the descriptor. Though that would also complicate the rule both for implementors and authors because it provides two different functionalities behaving different in different contexts.

Linear gradients (like any other images) are covered by both proposals.

In Lea's proposal:

@image --rainbow-circle {
  src: linear-gradient(red, yellow, lime, blue, purple);
  width: 400px;
  aspect-ratio: 1;
  clip-path: circle(400px);
}

.rainbow {
  background-image: image(--rainbow-circle);
  background-size: contain;
}

My proposal:

@image-manipulation --rainbow-circle {
  width: 400px;
  aspect-ratio: 1;
  clip-path: circle(400px);
}

.rainbow {
  background-image: manipulate-image(linear-gradient(red, yellow, lime, blue, purple), --rainbow-circle);
  background-size: contain;
}

@booluw:

Doesn't the width here causes a conflict with background-size property? Having two different widths might just leave room for buggy implementation.

@jsnkuhn expected the at-rule to work on the box model. Though the idea is to let it work on the image.

A width in that rule means to manipulate the image's intrinsic width. The same applies to height and aspect-ratio. So, if an image has a size of 1000px x 1000px and you apply width: 500px to it, it's intrinsic size is then 500px x 1000px. And that image can then be used with background-size. So it is like you initially provided an 500px x 1000px image. (See the example above where the generated image has an intrinsic width of 400px but is then resized via background-size: contain based on the size of the element it is used in.) And for clarity, providing a width or any other size-related descriptors shrink or stretch the image. If the image shall be cropped, then clip-path or object-fit should be used.

An issue that can arise is when images do not have an intrinsic size like gradients, colors or some forms of SVGs. For them it needs to be defined what happens when you provide percentages or other relative units like em.

Sebastian

booluw commented 2 years ago

Thanks @SebastianZ. I think the sizes of those SVGs and gradients should be the values defined in the manipulation rule, since the image is being created. That is; the image has it own box model, before being added to that of the element it is being applied to. Which implies that all box-model properties also work on the image.

ydaniv commented 2 years ago

There's another interesting use-case, I've mentioned it to @LeaVerou at CSSDay, having the @image-manipulation add filters to the image and have the browser cache that, so that animating it later, using transform for instance, will not cause browsers to melt down. We had these exp. at Wix, where when applying both at the same time caused havoc, but when animating a canvas that already applied the filters, i.e. with effects already composited, the results were order(s) of magnitude better.

So, the idea is to somehow hint to the browser that this image will-not-change, something like:

@image-manipulation --recolor-effect {
  filter: url(recolor-filter.svg#filter);
  will-change: none;
}

.bg-parallax {
  background-image: manipulate-image(image(url(bg.webp)));
}

And then animate .bg-parallax without extra damage.

jsnkuhn commented 2 years ago

apologies for the confusion I think that some of the example ideas I've posted might fall better under something like element().

So let me give this another try:

image

We have one background image that is then rotated in different ways to create slightly visually different backgrounds for the different links.

@image-manipulation rotate-x-y {
  transform: rotate(180deg);
}
@image-manipulation rotate-x {
  transform: rotateX(180deg);
}
@image-manipulation rotate-y {
  transform: rotateY(180deg);
}

a {background-image: url(yellow.webp);}

a:nth-of-type(2) { background-image: url(yellow.webp), rotate-x-y; }
a:nth-of-type(3) { background-image: url(yellow.webp), rotate-x; }
a:nth-of-type(4) { background-image: url(yellow.webp), rotate-y; }

in this case having to repeat the background-image: url(yellow.webp), bit seems a bit clunky. Maybe there a way to do something more like manipulation-name taking a cue from the already existing animation-name property?

SebastianZ commented 2 years ago

@booluw wrote:

I think the sizes of those SVGs and gradients should be the values defined in the manipulation rule, since the image is being created.

You are right that a new image is created by manipulating the source image. And with width, height and aspect-ratio you can define the sizes of the manipulated image. Though my question was how would a width: 80%; be interpreted when the source image doesn't have an intrinsic width? I guess, the answer in that case is that the width of the manipulated image is undefined as well.

That is; the image has it own box model, before being added to that of the element it is being applied to. Which implies that all box-model properties also work on the image.

I wouldn't say that the image has its own box model. There are properties like padding or box-sizing that don't apply to it.

@ydaniv wrote:

There's another interesting use-case, I've mentioned it to @LeaVerou at CSSDay, having the @image-manipulation add filters to the image and have the browser cache that, so that animating it later, using transform for instance, will not cause browsers to melt down.

I expected the images generated by an @image-manipulation rule to always be cached because the manipulations are only applied once to an image. Or are there use cases, in which dynamically applying the rules is required?

@jsnkuhn wrote:

We have one background image that is then rotated in different ways to create slightly visually different backgrounds for the different links.

@image-manipulation rotate-x-y {
  transform: rotate(180deg);
}
@image-manipulation rotate-x {
  transform: rotateX(180deg);
}
@image-manipulation rotate-y {
  transform: rotateY(180deg);
}

a {background-image: url(yellow.webp);}

a:nth-of-type(2) { background-image: url(yellow.webp), rotate-x-y; }
a:nth-of-type(3) { background-image: url(yellow.webp), rotate-x; }
a:nth-of-type(4) { background-image: url(yellow.webp), rotate-y; }

in this case having to repeat the background-image: url(yellow.webp), bit seems a bit clunky. Maybe there a way to do something more like manipulation-name taking a cue from the already existing animation-name property?

The proposed syntax would actually look like this:

a:nth-of-type(2) { background-image: manipulate-image(url(yellow.webp), rotate-x-y); }
a:nth-of-type(3) { background-image: manipulate-image(url(yellow.webp), rotate-x); }
a:nth-of-type(4) { background-image: manipulate-image(url(yellow.webp), rotate-y); }

I know, that makes it even longer to write. Though manipulate-image() generates an <image> value. That value can be used in many different properties like border-image-source, mask-image, list-style-image, etc. And each one could have its own image manipulation applied. Therefore, a property like manipulation-name (or rather background-image-manipulation) that is bound to another property wouldn't make sense.

Though I assume the final name for the image manipulation function probably won't be that long.

Sebastian

ydaniv commented 2 years ago

@SebastianZ

I expected the images generated by an @image-manipulation rule to always be cached because the manipulations are only applied once to an image. Or are there use cases, in which dynamically applying the rules is required?

Other use cases are the old filter() and others mentioned at the top, which could potentially be animated. So does this rule imply immutability of the result without the ability to animate its properties? I guess further animations could be applied in properties outside of the manipulation effects, which is reasonable.

booluw commented 2 years ago

@SebastianZ

You are right that a new image is created by manipulating the source image. And with width, height and aspect-ratio you can define the sizes of the manipulated image. Though my question was how would a width: 80%; be interpreted when the source image doesn't have an intrinsic width? I guess, the answer in that case is that the width of the manipulated image is undefined as well.

I think a width: 80% should be interpreted in relation to the width of the elment the image is been applied to.

SebastianZ commented 2 years ago

@ydaniv wrote:

I expected the images generated by an @image-manipulation rule to always be cached because the manipulations are only applied once to an image. Or are there use cases, in which dynamically applying the rules is required?

Other use cases are the old filter() and others mentioned at the top, which could potentially be animated. So does this rule imply immutability of the result without the ability to animate its properties? I guess further animations could be applied in properties outside of the manipulation effects, which is reasonable.

I see. Regarding the filter() function, interpolation (and with that animation) is already defined in the Filter Effects specification.

Though I can now see the point for allowing to animate them. One way to achieve that with the proposed syntax would be:

@image-manipulation --rotate-image {
  transform: rotate(359deg);
}

@keyframes --rotating-background {
  from: { background-image: url("background.jpg"); }
  to: { background-image: manipulate-image(url("background.jpg"), --rotate-image); }
}

.rotating-background {
  animation: 35.9s --rotating-background infinite;
}

Alternatively, we might define the animation directly within the at-rule, i.e. allow the animation-* properties as descriptors for the at-rule. Though as far as I know, the descriptors of at-rules aren't animatable so far. So that would be a novelty. @LeaVerou Feel free to correct me on this.

So an example for that could then look like this:

@keyframes --rotate {
  from: { transform: rotate(0deg); }
  to: { transform: rotate(359deg); }
}

@image-manipulation --rotate-image {
  animation: 35.9s --rotate infinite;
}

.rotating-background {
  background-image: manipulate-image(repeating-url("background.jpg"), --rotate-image);
}

@booluw wrote:

You are right that a new image is created by manipulating the source image. And with width, height and aspect-ratio you can define the sizes of the manipulated image. Though my question was how would a width: 80%; be interpreted when the source image doesn't have an intrinsic width? I guess, the answer in that case is that the width of the manipulated image is undefined as well.

I think a width: 80% should be interpreted in relation to the width of the elment the image is been applied to.

Making the manipulations dependent on the element they are used in, would cause significant performance issues as mentioned by @ydaniv. So if you animated the element's width, you'd have to run the manipulation rules for each frame of the animation and each element the rules are applied to.

Sebastian

SebastianZ commented 2 years ago

Given the very positive feedback so far, let's add this to the agenda. Points to discuss:

Sebastian

Crissov commented 2 years ago

If this feature was about image creation, it should not be limited to a single layer, i.e. it would require nesting or additive sources.

@image --composite {
  @layer --stage {
    src: "backdrop.jpg";
  }
  @layer --scene {
    src: "parallax.png";
  }
  @layer --actor {
    src: "map.svg#sprite";
    position: 200px 300px;
  }
}

I believe this would be suggested by the generic name @image. Otherwise, a different and more specific name should be chosen, e.g. @image-effect.

SebastianZ commented 2 years ago

This issue is about image manipulation, i.e. creating variations of existing images. This is what the very first sentence of it says. I am not completely against allowing the at-rule to also create completely new ones, though I am strongly in favor of restricting it to manipulation and rely on the existing methods to provide the source images, namely url(), image-set(), image(), *-gradient(), etc. This allows to provide context sensitive images. And it makes implementations easier as they just have to provide one way of image sources.

Sebastian

SebastianZ commented 2 years ago

Ah, and the use case of compositing images is a different one which should be discussed separately.

Sebastian

bramus commented 2 years ago

That @image / @image-manipulation block surely looks a lot like @mixin, no? Shouldn’t we then name it that way (or some other generic term), to leave the door open for future use-cases?

Note: not talking about its usage using the manipulate-image() / image() function here; only targeting the at-rule / definition part.

SebastianZ commented 2 years ago

@bramus I'm with you to keep the name @image for future use cases, e.g. like combining the image manipulations discussed here with the compositing features mentioned by @Crissov, image creation features and possible other features.

Sebastian

LeaVerou commented 2 years ago

This is already complex enough, if we add layers and other bells and whistles it's simply not going to happen in the forseeable future. I suggest we stay focused on the syntax that is needed to satisfy the majority of use cases, and not design around edge cases.

tabatkins commented 2 years ago

That @image / @image-manipulation block surely looks a lot like @mixin, no?

Can you elaborate on this? I don't see any resemblance.

bramus commented 2 years ago

That @image / @image-manipulation block surely looks a lot like @mixin, no?

Can you elaborate on this? I don't see any resemblance.

If you look at this example (as seen in the first post)

@image --foo {
    src: url("foo.png");
    aspect-ratio: 1 / 1;
    width: 100vw;
    object-fit: cover;
    filter: blur(10px);
    opacity: .5;
    transform: rotate(5deg);
}

… it’s an at-rule, an identifier, and a block with bunch of properties with assigned values (except for src which isn’t a CSS property).

A basic mixin such as the one below (as seen in the Sass docs), also follows that same structure

@mixin reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
SebastianZ commented 2 years ago

That @image / @image-manipulation block surely looks a lot like @mixin, no?

Can you elaborate on this? I don't see any resemblance.

If you look at this example (as seen in the first post)

… it’s an at-rule, an identifier, and a block with bunch of properties with assigned values (except for src which isn’t a CSS property).

A basic mixin such as the one below …

The suggested at-rule shares the syntax with a mixin like basically any at-rules with bodies but besides that there is no relation to mixins at all. An @mixin in Sass can have arbitrary properties and nested rules within it. The at-rule suggested here is restricted to a bunch of descriptors resembling properties (but which aren't properties) that are used to manipulate the given image. E.g. it does not allow padding: 0; or list-style: none;.

To summarize them, the descriptors proposed so far are:

Which ones actually make it into the at-rule is still to be discussed.

Sebastian

tabatkins commented 2 years ago

Yeah, the similarity there appears to be just "follows standard CSS syntactic patterns", but the same can be said of almost any at-rule. @counter-style and @font-face also look like that, for example, but I don't think it's reasonable to say they're "like @mixin". At-rules containing descriptors is just how CSS things are written.

In particular, @mixin specifically contains properties that'll eventually apply to an element; @image here just contains descriptors that resemble existing properties but won't ever apply to an element in the page. (And nothing prevents us from adding descriptors that go beyond what properties can currently do, either.)

SebastianZ commented 2 years ago

And I already start with the discussion. 😃

Any other suggestions? Did I miss anything from above?

Sebastian

LeaVerou commented 2 years ago

Any other suggestions? Did I miss anything from above?

I think padding could be useful, for adding transparent spacing around the main image. Maybe border too?

jsnkuhn commented 2 years ago

what would the difference be between padding, margin and clip-path: inset(); here?

SebastianZ commented 2 years ago

I think padding could be useful, for adding transparent spacing around the main image. Maybe border too?

what would the difference be between padding, margin and clip-path: inset(); here?

Right, I took margin from your initial proposal for that, @LeaVerou. clip-path: inset(); is different because it clips the image. I imagine margin to add extra transparent space around the image. It might be replaced by padding so that the background shines through if set (which would also be transparent by default but can be changed via the background-color property [and other background-* properties if we decide to add them, too]).

Sebastian

ydaniv commented 2 years ago

Although we're only in the context of manipulation of an existing image, I'd still like to stress the need for compositing and/or blending multiple images/colors. As a way of hinting to the UA to cache this in a single layer that "will not change" , so that we can more easily apply other effects on it, say scroll-animations. Kind of bringing WebGL-powers down to the masses 😄

SebastianZ commented 2 years ago

Looking at a related example syntax I previously provided, let me think that transform-origin should also be considered. I've added it to the list above.

Sebastian

SebastianZ commented 2 years ago

@bramus wrote:

That @image / @image-manipulation block surely looks a lot like @mixin, no? Shouldn’t we then name it that way (or some other generic term), to leave the door open for future use-cases?

Now I finally understand what you meant with that. We should give this rule a general name so that it can be extended in the future and restrict it to image manipulations for now.

If that's what you meant by your sentence, then I guess I'm fine with that. Though I want to stress that image creation is a topic on its own and needs separate discussion, especially regarding context-sensitivity as mentioned in a previous comment.

Sebastian

bramus commented 2 years ago

Now I finally understand what you meant with that. We should give this rule a general name so that it can be extended in the future and restrict it to image manipulations for now.

Yes, that’s what I meant by that. Was purely looking at the “shape” of how it was defined. But as @tabatkins pointed out that’s because it “follows standard CSS syntactic patterns” – Same can be said for other at-rules such as @font-face, @scroll-timeline (RIP), … The listed examples reminded me of them, mainly because many of the descriptors have a directly matching property. This was a wrong path to follow.

Generalizing also doesn’t seem a good idea the more I think about it, especially if an author would want to feature detect this using at-rule().

tl;dr disregard my mentions of mixins

SebastianZ commented 2 years ago

Generalizing also doesn’t seem a good idea the more I think about it, especially if an author would want to feature detect this using at-rule().

Authors can do feature detection, e.g. via at-rule(@image; corner-shape: angle), indenpendent of the name of the rule.

Though thinking more about it and looking at possible additions like compositions, I still believe it's better to handle image creation separately from image manipulation. And if we use a more specific name now, it is still possible to turn it into an alias for a more general one, but not the other way round.

Sebastian

jsnkuhn commented 2 years ago

another example from the wild that I think might be covered (https://www.fivebelow.com/):

image

a radial-gradient pattern image that is then rotated and clipped at the top? Would there need to be a scale with the rotate to make sure there are no transparent bit at the corners?

SebastianZ commented 2 years ago

We talked about resolution of the source image a lot, though we didn't talk about setting the resolution of the manipulated image. CSS Images 4 defines an image-resolution which allows to overwrite the resolution of all the images used in or on an element. For @image it could be reused to change the resolution of the manipulated image. So I've added it to the list above.

Sebastian

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed @image rule for manipulating images, and agreed to the following:

The full IRC log of that discussion <heycam> Topic: @image rule for manipulating images
<heycam> github: https://github.com/w3c/csswg-drafts/issues/6807
<heycam> TabAtkins: this is a request for feedback and possible WD, precised details are being discussed on the thread
<heycam> ... this is a request for a way to manipulate images directly in CSS
<heycam> ... right now you can do a handful of things directly in CSS
<heycam> ... certain repetitions with background properties
<heycam> ... but anything more complex, running a filter on it, opacity, etc. your only choice is to use a separate image, or to put it on a declarative element and then apply CSS properties to that element
<heycam> ... this is trying to split the different, and allow existing existing effecfts (transforms, filters), directly in a CSS image
<heycam> ... rough approach is an @image rule, which provides a doubel dashed name
<heycam> ... a bunch of descriptors, which takes a src image, then others which are copies of other CSS properites that do useful things
<heycam> ... currently only supporting things you can already do to elements
<heycam> ... once defined, you could use this image in CSS
<heycam> ... image(--foo)
<Rossen_> q?
<heycam> q+
<Rossen_> ack heycam
<fantasai> heycam: Alternative approach is to slam all of these filtering and transforms into the image() function itself
<fantasai> heycam: if you need to re-use that, put it all into a custom property
<fantasai> heycam: have you thought about that?
<heycam> TabAtkins: I don't believe there's a great reason to go one way or the other
<heycam> ... back in the day, using it in an image() function you'd need to repeat it
<heycam> ... but with variables it's not a huge deal now
<heycam> ... some bits of functionality like image layering have been discussed, but more widely as pulling in SVG's filter capabilities via nested at rules
<heycam> ... having them be consistent in approach would be nice
<heycam> ... but now that I bring that up, it could still be done using CSS functions
<heycam> ... it would be a matter of what we find more convenient to do, or more natural
<JakeA> q+
<heycam> ... don't think there's a huge way to go one way or the other
<Rossen_> ack dbaron
<heycam> dbaron: could you talk briefly about how sizing of these images works?
<heycam> ... looks like there's some sizing related properties in there
<heycam> ... transforms and filters which might have sizing implementations
<heycam> TabAtkins: you have a width/height property
<heycam> ... that's the natural width/heihgt of the produced image
<heycam> ... scaling etc. would be purely visual effects
<andreubotella> q+
<heycam> ... it would scale it within the bounds of the width/height that's already been provided
<Rossen_> ack JakeA
<heycam> JakeA: for blurring a low quality image as a placeholder
<heycam> ... that's difficult with filters now, because it blurs the edge as well
<heycam> ... here you want to recognize the edge and not blur the transparency into it
<heycam> ... so it works a bit more like backdrop-filter
<Rossen_> ack andreubotella
<heycam> andreubotella: could you use such an @image rule in the src property?
<heycam> ... if you already have declared an image, then you want to apply some further transformations to that?
<heycam> ... and for the current properties, it seems like you could apply a set of properties to multiple images
<heycam> TabAtkins: the exact answer to that depends on what we want to define
<heycam> ... being able to put a generated image in as the source of a new generated image is reasoanble
<ydaniv> q+
<heycam> ... whether that gets rasterized then transformed, as if it's an independent image, or these transformations are additively appended to the list of existing transformations in some intelligent fashion? no opinion
<heycam> ... both have pros/cons. simplicity on one side, understandability
<heycam> ... avoiding rasterization effects
<heycam> ... both seems potentially reasonable
<heycam> ... can discuss those
<heycam> andreubotella: thinking also maybe not having it rasterized, that might limit the properties we could add to @image rules
<heycam> TabAtkins: I think everythign we've discssed so far in the thread is compatible with just holding it as a scene graph
<heycam> ydaniv: might be some advantage to have this rasterized beforehand
<Rossen_> ack ydaniv
<heycam> ... if you're just setting the properties outside the at rule, you could have them animated
<heycam> ... then if you want to do further animations on the element, then have the browser apply all of these at the same time every frame, could be heavy for the browser to handle
<heycam> ... so maybe this could be some sort of performance boost
<heycam> ... another thing that might be way off, if you're thinking about syntax, having this for replaced images, object manipulation, that could be useful
<heycam> ... so not just for background-image, <image> values, but use the same manipulations on replaced elements like straight on an <img> element
<heycam> TabAtkins: right now you can take any replaced element and put properties on it
<heycam> ... but you want to be able to run these on the underlying image itself, and still be displayed in the bounds of the image element? so apply a rotate to the source image?
<heycam> ydaniv: I want to optimize for having the src already rasterized, then if I'm rotating the image outside, then the browser doesn't have to handle rasterizing every frame
<heycam> ... what you're saying is another use case which is also valuable
<Rossen_> ack dbaron
<Zakim> dbaron, you wanted to talk about ordering
<heycam> dbaron: Andreu's comment made me think about another set of questions about the ordering of these operations
<heycam> ... in CSS, a bunch of these things are properties where the order that they apply in is defined by our processing model
<heycam> ... the relative ordering we apply opactiy, filter, transform, etc. on a single element is defined by CSS's processing model
<heycam> ... one thought is that re being able to nest the output of one of these as the source of another, that's probably quite useful, since sometimes you want to apply these things in a differetn order than how CSS's processing model works
<heycam> TabAtkins: os the same descriptor multiple times in different fashions
<heycam> s/os/or/
<heycam> dbaron: maybe falling back to that processing model isn't the right thing here? for this rule, the graphical operations maybe shouldn't be order independent?
<heycam> ... so the order of the descriptors matters
<heycam> TabAtkins: if you have them in an order, that implies you should be able to repeat: rotate, blur, rotate
<heycam> ... first, object model issues
<heycam> ... also, that would run into our forward compat fallback ability
<heycam> ... where you can say something twice and ignore the first, unsupported value
<heycam> ... but I like the way you are thinking
<heycam> ... having this order be controllable is probably a great idea
<Rossen_> q?
<emilio> q+
<Rossen_> ack emilio
<heycam> emilio: that kind of issue disappears if you add the operations in the image() function
<heycam> TabAtkins: yes, it removes the fact taht ordering and repetition is ap roblem
<heycam> ... now support is an all or nothing for the entire graph
<heycam> ... on the other hand, if an image transform isn't supported, and you are trying to rely upon it, you probably do want the whole thing to fail
<heycam> ... so that is an argument in favor of a function
<heycam> q+
<emilio> heycam: I realized that there might be a semantic difference between image() and the at rule
<Rossen_> ack heycam
<emilio> ... each of those are a different image
<emilio> ... is there any situation where having the image defined in the at-rule lets you have a single place where you can do image manipulations, where with image() you'd need to apply the transform to all uses of the image() function
<emilio> ... it's probably not an issue, I don't think they can express different things
<emilio> TabAtkins: being able to say that one in an at-rule there's an efficiency arg to be made there
<heycam> TabAtkins: how to animate an at rule is an open question
<heycam> ... how to animate a function is already a reasonably solved problem
<heycam> ... match up lists, animate individual parameters
<heycam> TabAtkins: does the WG feel this is OK, or objectiosn to the approach in general?
<heycam> fantasai: my main concern is prioritization wrt other things we're doing
<heycam> ... other than that doesn't seem terrible
<heycam> Rossen: from a use case approach, it's fine
<heycam> fantasai: definitely needs a lot more work
<heycam> Rossen: that's like most other work we take on
<heycam> fantasai: this is not a small task
<heycam> Rossen: assuming Mia and others will be contributing to this?
<heycam> TabAtkins: I'm shepherding this
<andreubotella> /s/Mia/Lea/
<heycam> Rossen: acknowledge this adds more work to the group, but I'm hearing support for the use case and approach
<heycam> ... so I'm personally OK with this
<fantasai> s/Mia/Lea/
<heycam> TabAtkins: do we want this added to images 5, or is it larger enough to be split out?
<heycam> fantasai: what else is in images 5?
<heycam> Rossen: almost nothing
<heycam> fantasai: current images is images-4. images-5 is just an ED, don't think there's anything even in it
<heycam> TabAtkins: just want to know if it should be merged into the totality of the images spec
<heycam> RESOLVED: Accept @image and add it to css-images-5
SebastianZ commented 2 years ago

It's getting late here, but just some quick thoughts on the meeting discussion:

... rough approach is an `@image` rule, which provides a doubel dashed name ... a bunch of descriptors, which takes a src image, then others which are copies of other CSS properites that do useful things ... currently only supporting things you can already do to elements ... once defined, you could use this image in CSS ... image(--foo)

This was the initial proposal by @LeaVerou but the discussion since moved towards a rule purely used for manipulating the images and using a function to specify the source image.

heycam: Alternative approach is to slam all of these filtering and transforms into the image() function itself

This is definitely an interesting approach that would probably make animation easier and easily allow applying manipulations in order. Though I assume it may also quickly get quite complicated syntax-wise. I still need to give that some deeper thought. But it also doesn't seem to exclude each other.

dbaron: could you talk briefly about how sizing of these images works? ... looks like there's some sizing related properties in there ... transforms and filters which might have sizing implementations TabAtkins: you have a width/height property ... that's the natural width/heihgt of the produced image ... scaling etc. would be purely visual effects

I imagined width: auto; and height: auto; to be possible, meaning that transforms and effects like blurs would change the size of the canvas to fit in the manipulated image.

Imagine you rotate an image by 30 degrees and you want it not to be clipped. Without auto-sizing, you have to calculate the new size of the canvas yourself, then calculate the padding that is needed to get that size to finally be able to rotate the image.

fantasai: what else is in images 5? Rossen: almost nothing fantasai: current images is images-4. images-5 is just an ED, don't think there's anything even in it

The repository doesn't hold any images-5 ED yet. There are just a few issues labelled with css-images-5.

Sebastian