0

(While this question has to do with a concrete coding dilemma, it's mostly about what's the best way to design a function.)

I'm writing a method that should take two Color objects, and gradually transform the first Color into the second one, creating an animation. The method will be in a utility class.

My problem is that Color is an immutable object. That means that I can't do color.setRGB or color.setBlue inside a loop in the method.

What I can do, is instantiate a new Color and return it from the method. But then I won't be able to gradually change the color.

So I thought of three possible solutions:


1- The client code includes the method call inside a loop. For example:

int duration = 1500; // duration of the animation in milliseconds
int steps = 20; // how many 'cycles' the animation will take
for(int i=0; i<steps; i++) color = transformColor(color, targetColor, duration, steps);

And the method would look like this:

Color transformColor(Color original, Color target, int duration, int steps){
    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..

    Thread.sleep(duration / STEPS); // exception handling omitted
    return new Color(newRed, newGreen, newBlue);
}

The disadvantage of this approach is that the client code has to "do part of the method's job" and include a for loop. The method doesn't do it's work entirely on it's own, which I don't like.


2- Make a mutable Color subclass with methods such as setRed, and pass objects of this class into transformColor. Then it could look something like this:

void transformColor(MutableColor original, Color target, int duration){
    final int STEPS = 20;

    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..

    for(int i=0; i<STEPS; i++){
        original.setRed(original.getRed() + redAddition);
        // same for green and blue ..
        Thread.sleep(duration / STEPS); // exception handling omitted
    }
}

Then the calling code would usually look something like this:

// The method will usually transform colors of JComponents
JComponent someComponent = ... ;
// setting the Color in JComponent to be a MutableColor
Color mutableColor = new MutableColor(someComponent.getForeground());
someComponent.setForeground(mutableColor);
// later, transforming the Color in the JComponent
transformColor((MutableColor)someComponent.getForeground(), new Color(200,100,150), 2000);

The disadvantage is - the need to create a new class MutableColor, and also the need to do casting.


3- Pass into the method the actual mutable object that holds the color. Then the method could do object.setColor or similar every iteration of the loop.

Two disadvantages:

A- Not so elegant. Passing in the object that holds the color just to transform the color feels unnatural.

B- While most of the time this method will be used to transform colors inside JComponent objects, other kinds of objects may have colors too. So the method would need to be overloaded to receive other types, or receive Objects and have instanceof checks inside.. Not optimal.


Right now I think I like solution #2 the most, than solution #1 and solution #3 the least. However I'd like to hear your opinions and suggestions regarding this.

  • 2
    Mutating the color under the rendering system's feet from another thread seems neither good style nor particularly robust. – user7043 Jun 5 '14 at 17:47
4

I'm writing a method that should take two Color objects, and gradually transform the first Color into the second one, creating an animation. The method will be in a utility class.

OK, so how many unrelated concerns do we have in that sentence?

  1. a method that should take two Color objects, and gradually transform the first Color into the second one - OK, that's a thing!

    I'm slightly concerned at your use of the word "gradual" since it could imply mixing the concept of time with the concept of colour. However, I can (and will) choose to read the other meaning, as in gradations of colour. So, we're taking two colours and (somehow) producing a given number of intermediate shades. You haven't specified whether we should do this in RGB, HSV, CMYK or whatever other colour space, but fine.

  2. creating an animation - well, that sounds like a completely separate thing. The function above could be used in, say, creating a colour gradient for shading (where your colour varies in space instead of time). So why tie it to the animation thing?

So now we know we want some function to produce an ordered sequence of colours given some inputs. This could be a container, or it could be an iterator, or it could be a straight loop taking some callback function. These are all similar, with the following trade-offs:

  • return a container: uses more storage (the other two are constant size, and this scales linearly with the number of steps), and front-loads the computation (it takes a while to populate that container if it's large)
  • iterator: the client code still needs to write that for loop you objected to, and you need to write an iterator type (unless your language has co-routines or something similar, like Python's generators) but it's very efficient
  • callback: the client code makes a single call, but instead of the for loop, it has to write some callback function with access to all the right state (unless your language has nice lambdas or lexical closures)

The container option is just for completeness, and I'd recommend either of the other two - depending on preference and language support - unless you know it'll be small or you have no other option.

1

Here's a case where you can actually use a pattern: Iterator

Your iterator object is constructed with the start and target colors, and a number of steps. Each call to next() returns the next color in the sequence, until you reach the end.

I see that your example includes a sleep, which is an incredibly bad idea for GUI programming. Instead, use a javax.swing.Timer to trigger reads from your iterator. If you want to encapsulate the duration in the iterator object (a good idea), you can add a sleepTime() method.

0

An alternative to an iterator is to pass in whatever you want to do with the color as an argument to transformColor.

void transformColor(Color original, Color target, int duration, int steps, Function<Color, Void> f){
    int redDiff = target.getRed() - original.getRed();
    int redAddition = redDiff / steps;
    int newRed = original.getRed() + redAddition;

    // same for green and blue ..
    for (int i = 0; i < steps; i++) {
        f(new Color(newRed, newGreen, newBlue));
        Thread.sleep(duration / STEPS); // exception handling omitted
    }
}

On that note I'd argue it's surprising and potentially undesirable for transformColor to take care of timing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.