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

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

Open
LeaVerou opened this issue Nov 9, 2021 · 57 comments
Open

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

LeaVerou opened this issue Nov 9, 2021 · 57 comments

Comments

@LeaVerou
Copy link
Member

LeaVerou commented Nov 9, 2021

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:

  • src: <image>: Mandatory, sets the image we are manipulating. Can be any CSS <image> including gradients etc.
  • width, height, inline-size, block-size to override intrinsic dimensions. Percentages resolve relative to original intrinsic size OR viewport (not sure which one makes more sense; at least the latter is always available)
  • aspect-ratio to override intrinsic aspect ratio
  • margin to add (transparent) spacing around image
  • object-fit
  • opacity
  • filter
  • transform and friends (translate, scale, rotate)
  • clip-path
  • mask

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:

  • It solves the problem using syntax that authors are already familiar with. Instead of needing to learn different ad hoc ways of transforming images that are specific to each use case, they only need to learn one @image rule and then they can even guess the descriptors they need as they are essentially a subset of existing CSS properties.
  • It's designed to be easy to extend in the future to solve more image manipulation use cases.
  • While lengthier, it's nicer to read than functional syntax like filter(). Compare:
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 :

  • 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).
  • 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)
@tabatkins
Copy link
Member

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
Copy link

booluw commented Nov 10, 2021

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

@faceless2
Copy link

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
Copy link
Member Author

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
Copy link
Member

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
Copy link

jsnkuhn commented Nov 24, 2021

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

@LeaVerou
Copy link
Member Author

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
Copy link
Contributor

SebastianZ commented Mar 8, 2022

@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
Copy link
Member Author

LeaVerou commented Mar 8, 2022

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

@jsnkuhn
Copy link

jsnkuhn commented Apr 21, 2022

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

@booluw
Copy link

booluw commented Apr 21, 2022

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

@jsnkuhn
Copy link

jsnkuhn commented Jun 3, 2022

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
Copy link

jsnkuhn commented Jun 10, 2022

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
Copy link
Contributor

SebastianZ commented Jun 10, 2022

@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
Copy link
Contributor

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

  • @image-change / change-image()
  • @image-edit / edit-image()
  • @image-modification / modify-image()
  • @image-alternation / alter-image()
  • @image-adjust / adjust-image()
  • @image-adaptation / adapt-image()
  • @image-variation / vary-image()
  • @image-mutation / mutate-image()
  • @image-customization / customize-image()
  • @image / image() (reusing the existing image() function and introducing a new syntax to it may probably be a no-go, though)

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

Sebastian

@LeaVerou
Copy link
Member Author

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

@booluw
Copy link

booluw commented Jun 12, 2022

@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
Copy link

booluw commented Jun 12, 2022

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
Copy link
Contributor

SebastianZ commented Jun 12, 2022

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
Copy link

booluw commented Jun 12, 2022

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
Copy link
Contributor

ydaniv commented Jun 12, 2022

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
Copy link

jsnkuhn commented Jun 12, 2022

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
Copy link
Contributor

SebastianZ commented Jun 12, 2022

@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

@SebastianZ
Copy link
Contributor

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
Copy link
Member

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

  • RESOLVED: Accept @image and add it to css-images-5
The full IRC log of that discussion <heycam> Topic: @image rule for manipulating images
<heycam> github: https://github.com//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
Copy link
Contributor

SebastianZ commented Sep 15, 2022

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

@jsnkuhn
Copy link

jsnkuhn commented Sep 21, 2022

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?

asking this a different way: would rotate as part of image-manipulation rotate the individual iterations of a repeated image or the whole pattern of that image as created after the repeat is applied?

@tabatkins
Copy link
Member

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.

Nah, not particularly, it would just be image(<source>, <manipulation-fn>*), where all the manipulation-fns are things like repeat(...) or rotate(...). For larger sets of transforms you'd likely format it similarly to an at-rule, like:

:root {
  --hero-image: image("hero-base.png",
    rotate(...)
    repeat(...)
    sepia(...)
  );
}

@SebastianZ
Copy link
Contributor

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.

Nah, not particularly, it would just be image(<source>, <manipulation-fn>*), where all the manipulation-fns are things like repeat(...) or rotate(...). For larger sets of transforms you'd likely format it similarly to an at-rule, like:

:root {
  --hero-image: image("hero-base.png",
    rotate(...)
    repeat(...)
    sepia(...)
  );
}

The downside of that is that is that it doesn't align with existing properties and you might not be able to express everything with that like e.g. width or height in combination with aspect-ratio or object-fit.
Though it does seem easier to understand in regard of the order of manipulations and it is more condense.

Sebastian

@LeaVerou
Copy link
Member Author

LeaVerou commented Dec 15, 2023

A few more thoughts.

Scaling

Thinking about this a bit more, I don’t think we should restrict ourselves to descriptors that correspond to CSS properties. Obviously when concepts match we should re-use CSS property names as much as possible, but there are many concepts that are useful for manipulating images, but do not have a corresponding CSS property.

It seems to me that @image can be a big win for things like sizing or repeat: Rather than adding properties for these in everything that can accept an image, bake them into the image.

For 9-slice scaling, there is no corresponding CSS property that would make sense, but it seems pretty straightforward to add a descriptor like:

fit: fill | none | scale-down | scale-up | contain | cover | sliced(<length>#{1,4}[ stretch | repeat | round | space ]{1,2}?) | <repeat-style>

or two separate descriptors (fit and repeat). Do note that since src refers to any <image>, @image rules can be chained to produce more complex effects.

We also need to be able to optionally decouple the source bounding box from the output bounding box. The original proposal includes size properties as overriding the image’s intrinsic dimensions, but assumes these will also be the intrinsic dimensions of the output.

Using @image for manipulations?

I think we could have our cake and eat it too, with an optional src descriptor. image(--foo) just uses what is specified in the src descriptor if one exists, or is IACVT if one does not exist, and image(--foo, url("foo.png")) overrides it. We could even specify it so that in the latter uses the image in src as a fallback.

@SebastianZ
Copy link
Contributor

@LeaVerou Your proposal somewhat overlaps with object-fit mentioned earlier. And you're mixing scaling and tiling.

The use case of tiling and repeating the image sounds reasonable. Though I wonder why we need a sliced() function and how you'd imagine that to work, especially outside of border images. Also, so far, all descriptors work in the context of the image. A sliced() function would have to work in the context in which the image is used, right?

Sebastian

@brandonmcconnell
Copy link

brandonmcconnell commented Jun 18, 2024

Using @image for manipulations?

I think we could have our cake and eat it too, with an optional src descriptor. image(--foo) just uses what is specified in the src descriptor if one exists, or is IACVT if one does not exist, and image(--foo, url("foo.png")) overrides it. We could even specify it so that in the latter uses the image in src as a fallback.

I like @LeaVerou's idea of supporting both creating and manipulating images via @image with an optional src descriptor.

Could we also support a use case for inline-defined images and manipulations for any images/manipulations that are intended for single use and do not need to be defined under a variable name for reuse? Hypothetically, this would be supported:

@image --circle {
  src: url(/some-path/background.png),
  width: 190px;
  aspect-ratio: 1;
  clip-path: circle(closest-side);
}

.category-link {
  background-image: image(--circle, { width: 100px });
  background-repeat: no-repeat;
  background-position: center center;
}

Any braced object inside image() would be treated as an inline @image rule declaration.

It may even prove an equally common use case to store a common set of rules for an image inside an @image but to set the src whenever used, like this:

@image --fancy {
  /* some rules */
}

#element-1 {
  background-image: image(--fancy, { src: '../some-image.png' });
}

#element-2 {
  background-image: image(--fancy, { src: '../some-other-image.png' });
}

@LeaVerou
Copy link
Member Author

LeaVerou commented Jun 19, 2024

@brandonmcconnell Yes, I think we should do this. Supporting complex use cases should not come at the cost of making simple use cases more complicated.

In fact, why not support both? src provides the default image, which can still be overridden at the point of use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests