# 1.0 Migration Notes

ColorAide has been a constantly evolving project. As we've pushed for a stable release, the 1.0 milestone was no
exception. For any of the early adopters, there are a number of things to be aware of when migrating to 1.0. In this
guide, we'll cover the changes most likely to impact users.

## Plugins

Plugins have gone through a number of reworks. In 1.0, we now require all plugins to be registered as instances. Prior
to 1.0 plugins were just passed in un-instantiated. Some plugins were used as static classes almost, and some (color
spaces) were instantiated on the creation of every color.

In 1.0, all plugins are required to be instantiated prior to registration. This gives the user the opportunity to
specify any alternative defaults if desired.

```py play
Color('red').delta_e('blue', method='cmc')
from coloraide.distance.delta_e_cmc import DECMC
class Custom(Color):
    DELTA_E = "cmc"
Custom.register(DECMC(l=1, c=1), overwrite=True)
Custom('red').delta_e('blue')
```

## Plugin Renames

ColorAide had some inconsistencies when it came to some plugin names. For instance, we would use variations of `Lch`,
`LCH`, etc. This made it more difficult to predict how a plugin was named, and remember it. In this case appropriate
casing is usually `LCh`. All plugins referring to `LCh` were renamed to be more consistent.

Outside of LCh related classes, there were a few additional renames to better match the color space of interest's real
name.

Old\ Name     | New\ Name
------------- | ---------
`Lch`         | `LCh`
`LchD65`      | `LChD65`
`Oklch`       | `OkLCh`
`Lchuv`       | `LChuv`
`Lch99o`      | `LCh99o`
`LchChroma`   | `LChChroma`
`OklchChroma` | `OkLChchroma`
`Lchish`      | `LChish`
`XyY`         | `xyY`
`Din99o`      | `DIN99o`
`SRGB`        | `sRGB`
`SRGBLinear`  | `sRGBLinear`
`ORGB`        | `oRGB`


## Default Plugins

Over the course of development, we've added a good number of color spaces. With the 1.0 release, it was decided to have
the default `Color` object not register all available color spaces out of the box as the amount of color spaces has
grown quite substantially.

As not all color spaces are registered by default, ∆E plugins tied to color spaces no longer registered by default will
also not be registered by default.

Lastly, since the `bradford` CAT is the default, it was deemed unnecessary to register all the other CAT plugins by
default.

With all of that said, all of the exiting plugins are still available and can be included if/when needed. If any of the
plugins that are no longer registered by default are needed, there are a couple of options:

1. The recommended way is to just subclass the `Color` object and cherry pick the plugins that are needed. When
   classing, all the plugins registered in the base will be copied over to the derived class. Then we can just pass
   in the instantiated plugins.

    ```py play
    from coloraide import Color as Base
    from coloraide.spaces.jzazbz import Jzazbz
    from coloraide.distance.delta_e_z import DEZ

    class Color(Base): ...

    Color.register([Jzazbz(), DEZ()])

    Color('red').convert('jzazbz')
    Color('red').delta_e('blue', method='jz')
    ```

2. We also provide a new color object derived from `Color` that includes **all** color spaces called `ColorAll`. This
   object won't be as light, but will provide quick and easy access to everything ColorAide offers. By default, it
   registers every plugin. It can be found under `coloraide.everything`.

    ```py play
    from coloraide.everything import ColorAll as Color

    Color('purple').convert('hunter-lab')
    ```

## Dynamic Properties/Functions and Coordinate Access

Prior to 1.0, ColorAide's `Color` object had color channel properties that would _magically_ mutate based on what the
current color space was that the object currently held. While cool, this added overhead to every class attribute access.
In an effort to dramatically reduce unnecessary overhead, this feature had to be rethought.

Additionally, ∆E methods were also added dynamically. For instance, if we had the ∆E 2000 distancing plugin registered,
we'd have access to ∆E via `Color.delta_e_2000` or `#!py3 Color.delta_e(color, method='2000')`. Again, the overhead
to _magically_ provide these properties the way we were posed the same problem as what was seen with dynamic color
channel properties

Additionally, the `Color` object used to have a `coords()` function to get all the non-alpha color channels. This
function was not really problematic, but as we decided a solution for the dynamic properties, it became apparent that
we would no longer need such a function.

1.0 removed the overhead of _magic_ dynamic properties and functions. In particular, we decided to approach color
channel access in a new and different way.

Moving forward, the `Color` object is now iterable and indexable. Channels can be directly indexed via channel names
or numerical indexes. You can even use slices:

```py play
color = Color('orange')
color
color['blue']
color[0]
color[:-1]
```

`Color` objects are also iterable, so you can just loop them as well, or cast the object as a list.

```py play
for channel in Color('orange'):
    print(channel)
list(Color('green'))
```

Setting channels is just as easy and can be done by indexing channels with names, numerical indexes, or even slices.

```py play
color = Color('transparent')
color[:] = [1, 0, 0, 0.5]
color
```

As far as ∆E methods are concerned, we already had two different ways to approach this, so we simply removed the
dynamic functions. To access any of the different ∆E methods, simply call the generic `delta_e` function and provide
the `method`.

```py play
Color('red').delta_e('green', method='2000')
```

## Gamut Mapping and Clipping

During our path to 1.0, we noticed that when performing gamut mapping and clipping, in most cases, we were performing
them "in place" instead of the default which generated new `Color` instances. There are times when we occasionally
wanted a new instance of the color when fitting a color to its gamut, but that turned out to not be the norm.

Generating new instances obviously will create more overhead, and in some cases, such as color mixing, returning a new
color opposed to mutating the existing one makes a lot more sense, but with gamut mapping and clipping, for efficiency,
we were often forcing "in place" operations.

1.0 now does gamut mapping and clipping in place by default. With this change, the `in_place` parameter is not longer
available for `fit()` and `clip()`.

So, if migrating to 1.0, if you were calling `fit()` and `clip()` directly, a few changes will need to be made. If you'd
like to do an in place gamut correction, simply call the function. If you'd like to generate a new instance, clone the
color first.

```py play
color1 = Color('display-p3', [1, 1, 0])
color1.fit('srgb')
color1
color2 = Color('display-p3', [0, 1, 0])
color3 = color2.clone().fit('srgb')
color2, color3
```

## Dictionary Output

The dictionary format for input and output as been simplified for the 1.0 release. Prior to 1.0, the `Color` object
used to export color dictionaries with the space name and each channel under an individually named key:

```py
{'space': 'srgb', 'r': 1, 'g': 0, 'b': 0, 'alpha': 1}
```

This required more overhead, particularly when parsing to handle channel alias and the like. For 1.0, we've streamlined
the format to export the data with all color coordinates under `coords` and the alpha channel still under `alpha`. This
makes streamlines the process of handling dictionaries as inputs and outputting them when requested, in turn, improving
performance.

```py play
d = Color('rebeccapurple').to_dict()
d
Color(d)
```

## Interpolation

Interpolation was an area we were generally unhappy with, so it was majorly overhauled.

Prior to 1.0, interpolation could be a bit awkward. Interpolation used to require the first color in the interpolation
to be the calling object, and all the rest had to be fed in.

```py
Color('red').interpolate(['blue', 'green', 'orange'])
```

When performing a simple mix, this felt natural and made sense:

```py
Color('red').mix('blue', 0.25)
```

But with long chains of colors, this just felt cumbersome. To remedy this, we changed the `interpolate` and `steps`
methods to `@classmethods`. We left `mix` as is since with two colors it feels natural.

So moving forward, `interpolate` and `steps` will execute interpolations from class methods.

```py play
Color.interpolate(['red', 'blue', 'green', 'orange'])
Color.steps(['red', 'blue', 'green', 'orange'], steps=10)
```

This means that you do not have to call the function from an instantiated object, and if you do, the instantiated color
that is making the call will not be included in the interpolation. Only the colors in the list are considered during the
interpolation.

```py play
Color('white').interpolate(['red', 'blue', 'green', 'orange'])
```

This will make even more sense as we highlight the other changes.

Another problem we faced was the awkwardness of color stops and easing functions. Before we used to have a `Piecewise`
object that you'd wrap a channel in to create color stops or inject easing functions and other various behaviors between
colors, but it had to be applied on the second color in the chain, and this didn't quite work for the first color.
If you wanted to add a stop to the first color, you then had to use a special `stop` parameter...it was unintuitive.

```py
from coloraide import Piecewise
Color('red').interpolate(['blue', Piecewise('orange', 0.75, progress=lambda t: t * 3), 'purple'], stop=0.25)
```

In 1.0, we simplified things greatly. Since `interpolate` and `steps` now require that all colors must be in the input
list if they are to be considered for interpolation, we can process them all in a consistent and more intuitive manner.

As before, `steps` and `interpolate` allow you to set function parameters to generally control the behavior for the
entire interpolation across all colors. You can also still add easing functions via `progress` which will also affect
the entire interpolation by default, but now you can inject easing functions directly between colors which will only
be applied between those two colors.

```py play
Color.interpolate(['red', lambda t: t * 3, 'orange', 'purple'])
```

You can also directly wrap any color in the list with `stop` to change the color stop position. Since the first color
is now treated like all the other colors, there is no need for the `stop` function parameter either.

```py play
from coloraide import stop
Color.interpolate([stop('red', 0.25), stop('orange', 0.75), 'purple'])
```

And if you are familiar with CSS color hinting, which essentially alters the midpoint between two color stops, we've
added a `hint` function which takes a new relative midpoint and returns a midpoint easing function which essentially
acts the same as CSS interpolation hints.

```py play
from coloraide import hint
Color.interpolate(['yellow', 'pink'])
Color.interpolate(['yellow', hint(0.25), 'pink'])
```

All of this makes for a less confusing experience when using interpolation. Additionally, all of the changes simplified
the logic allowing us to even add a new interpolation method!

```py play
Color.interpolate(['red', 'blue', 'green', 'orange'], method='bspline')
```

## Color Space Filters

In the beginning, the `Color` space object was created with a naive filtering system. It added a little overhead, but
the real issue was the fact that it _only_ filtered inputs through `new`, `match`, and through normal instantiation. It
did _not_ filter through almost any other method that accepted inputs. It was decided to leave color filtering up to the
user.

```py play
c = Color('display-p3', [1, 1, 0])
try:
    if c.space() not in ['srgb', 'hsl', 'hwb']:
        raise ValueError('Invalid Color Space')
except ValueError as e:
    print(e)
```
