Skip to content

Commit e8df656

Browse files
authored
Merge pull request #59716 from nextcloud/jtr/fix-color2
fix(Color): blueF() to return correct channel and tighten validation/docs
2 parents 3d7741f + f399636 commit e8df656

5 files changed

Lines changed: 78 additions & 63 deletions

File tree

lib/composer/composer/autoload_classmap.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,6 @@
13881388
'OC\\Collaboration\\Resources\\Manager' => $baseDir . '/lib/private/Collaboration/Resources/Manager.php',
13891389
'OC\\Collaboration\\Resources\\ProviderManager' => $baseDir . '/lib/private/Collaboration/Resources/ProviderManager.php',
13901390
'OC\\Collaboration\\Resources\\Resource' => $baseDir . '/lib/private/Collaboration/Resources/Resource.php',
1391-
'OC\\Color' => $baseDir . '/lib/private/Color.php',
13921391
'OC\\Command\\AsyncBus' => $baseDir . '/lib/private/Command/AsyncBus.php',
13931392
'OC\\Command\\CommandJob' => $baseDir . '/lib/private/Command/CommandJob.php',
13941393
'OC\\Command\\CronBus' => $baseDir . '/lib/private/Command/CronBus.php',

lib/composer/composer/autoload_static.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,32 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
1111
);
1212

1313
public static $prefixLengthsPsr4 = array (
14-
'O' =>
14+
'O' =>
1515
array (
1616
'OC\\Core\\' => 8,
1717
'OC\\' => 3,
1818
'OCP\\' => 4,
1919
),
20-
'N' =>
20+
'N' =>
2121
array (
2222
'NCU\\' => 4,
2323
),
2424
);
2525

2626
public static $prefixDirsPsr4 = array (
27-
'OC\\Core\\' =>
27+
'OC\\Core\\' =>
2828
array (
2929
0 => __DIR__ . '/../../..' . '/core',
3030
),
31-
'OC\\' =>
31+
'OC\\' =>
3232
array (
3333
0 => __DIR__ . '/../../..' . '/lib/private',
3434
),
35-
'OCP\\' =>
35+
'OCP\\' =>
3636
array (
3737
0 => __DIR__ . '/../../..' . '/lib/public',
3838
),
39-
'NCU\\' =>
39+
'NCU\\' =>
4040
array (
4141
0 => __DIR__ . '/../../..' . '/lib/unstable',
4242
),
@@ -1429,7 +1429,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
14291429
'OC\\Collaboration\\Resources\\Manager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Manager.php',
14301430
'OC\\Collaboration\\Resources\\ProviderManager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/ProviderManager.php',
14311431
'OC\\Collaboration\\Resources\\Resource' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Resources/Resource.php',
1432-
'OC\\Color' => __DIR__ . '/../../..' . '/lib/private/Color.php',
14331432
'OC\\Command\\AsyncBus' => __DIR__ . '/../../..' . '/lib/private/Command/AsyncBus.php',
14341433
'OC\\Command\\CommandJob' => __DIR__ . '/../../..' . '/lib/private/Command/CommandJob.php',
14351434
'OC\\Command\\CronBus' => __DIR__ . '/../../..' . '/lib/private/Command/CronBus.php',

lib/private/Color.php

Lines changed: 0 additions & 19 deletions
This file was deleted.

lib/public/Color.php

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
declare(strict_types=1);
34
/**
45
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
56
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -9,20 +10,21 @@
910

1011
/**
1112
* Simple RGB color container
13+
*
1214
* @since 25.0.0
1315
*/
1416
class Color {
15-
private int $r;
16-
private int $g;
17-
private int $b;
18-
1917
/**
2018
* @since 25.0.0
2119
*/
22-
public function __construct($r, $g, $b) {
23-
$this->r = $r;
24-
$this->g = $g;
25-
$this->b = $b;
20+
public function __construct(
21+
private int $r,
22+
private int $g,
23+
private int $b,
24+
) {
25+
self::assertChannelInRange($this->r, 'r');
26+
self::assertChannelInRange($this->g, 'g');
27+
self::assertChannelInRange($this->b, 'b');
2628
}
2729

2830
/**
@@ -62,7 +64,7 @@ public function greenF(): float {
6264
}
6365

6466
/**
65-
* Returns the green blue component of this color as an int from 0 to 255
67+
* Returns the blue color component of this color as an int from 0 to 255
6668
*
6769
* @since 25.0.0
6870
*/
@@ -76,65 +78,99 @@ public function blue(): int {
7678
* @since 25.0.0
7779
*/
7880
public function blueF(): float {
79-
return $this->g / 255;
81+
return $this->b / 255;
8082
}
8183

8284
/**
83-
* Returns the name of the color in the format "#RRGGBB"; i.e. a "#" character followed by three two-digit hexadecimal numbers.
85+
* Returns the hex triplet color value as a string ("#RRGGBB")
8486
*
8587
* @since 25.0.0
8688
*/
8789
public function name(): string {
8890
return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b);
8991
}
9092

93+
// Utility Functions
94+
9195
/**
92-
* Mix two colors
96+
* Generate a progression of colors starting with $color1 and moving toward $color2.
9397
*
94-
* @param int $steps the number of intermediate colors that should be generated for the palette
95-
* @param Color $color1 the first color
96-
* @param Color $color2 the second color
97-
* @return list<Color>
98+
* @param int $steps Total number of colors to return (including $color1, but excluding $color2); should be at least 2
99+
* @param Color $color1 The starting color (index 0 of the returned list)
100+
* @param Color $color2 The target color used to calculate the transition
101+
* @return list<Color> The list of colors starting with $color1 up to but not including $color2
98102
* @since 25.0.0
99103
*/
100104
public static function mixPalette(int $steps, Color $color1, Color $color2): array {
105+
if ($steps < 1) {
106+
// 1 is a hard requirement; 2 is a practical requirement
107+
throw new \InvalidArgumentException('Palette steps must be at least 1 (and should be at least 2).');
108+
}
109+
101110
$palette = [$color1];
102-
$step = self::stepCalc($steps, [$color1, $color2]);
111+
[$rDelta, $gDelta, $bDelta] = self::calculateDeltas($steps, $color1, $color2);
112+
103113
for ($i = 1; $i < $steps; $i++) {
104-
$r = intval($color1->red() + ($step[0] * $i));
105-
$g = intval($color1->green() + ($step[1] * $i));
106-
$b = intval($color1->blue() + ($step[2] * $i));
107-
$palette[] = new Color($r, $g, $b);
114+
$palette[] = new Color(
115+
// TODO: Consider using round() instead of (int) truncation for more accurate color transitions.
116+
(int)($color1->red() + ($rDelta * $i)),
117+
(int)($color1->green() + ($gDelta * $i)),
118+
(int)($color1->blue() + ($bDelta * $i)),
119+
);
108120
}
121+
109122
return $palette;
110123
}
111124

112125
/**
113-
* Alpha blend another color with a given opacity to this color
126+
* Blend this color over a source color.
127+
*
128+
* An opacity of 0 returns $source, and 1 returns this color.
114129
*
115-
* @return Color The new color
130+
* @param float $opacity Opacity of this color, expected in the range 0.0 to 1.0
131+
* @param Color $source The source/background color
132+
* @return Color The blended color
116133
* @since 25.0.0
117134
*/
118135
public function alphaBlending(float $opacity, Color $source): Color {
136+
if ($opacity < 0.0 || $opacity > 1.0) {
137+
throw new \InvalidArgumentException('Opacity must be between 0.0 and 1.0.');
138+
}
139+
119140
return new Color(
141+
// TODO: Consider using round() instead of (int) truncation for more accurate color transitions.
120142
(int)((1 - $opacity) * $source->red() + $opacity * $this->red()),
121143
(int)((1 - $opacity) * $source->green() + $opacity * $this->green()),
122144
(int)((1 - $opacity) * $source->blue() + $opacity * $this->blue())
123145
);
124146
}
125147

126148
/**
127-
* Calculate steps between two Colors
128-
* @param int $steps start color
129-
* @param Color[] $ends end color
130-
* @return array{0: float, 1: float, 2: float} [r,g,b] steps for each color to go from $steps to $ends
149+
* Calculate the per-channel change (RGB deltas) required to transition between two colors.
150+
*
151+
* @param int $count The number of intervals to divide the transition into >0
152+
* @param Color $start The starting color
153+
* @param Color $end The target color
154+
* @return array{0: float, 1: float, 2: float} The per-channel [r, g, b] increment required for each interval
131155
* @since 25.0.0
132156
*/
133-
private static function stepCalc(int $steps, array $ends): array {
134-
$step = [];
135-
$step[0] = ($ends[1]->red() - $ends[0]->red()) / $steps;
136-
$step[1] = ($ends[1]->green() - $ends[0]->green()) / $steps;
137-
$step[2] = ($ends[1]->blue() - $ends[0]->blue()) / $steps;
138-
return $step;
157+
private static function calculateDeltas(int $count, Color $start, Color $end): array {
158+
$deltas = [];
159+
160+
$deltas[0] = ($end->red() - $start->red()) / $count;
161+
$deltas[1] = ($end->green() - $start->green()) / $count;
162+
$deltas[2] = ($end->blue() - $start->blue()) / $count;
163+
164+
return $deltas;
165+
}
166+
167+
private static function assertChannelInRange(int $value, string $channel): void {
168+
if ($value < 0 || $value > 255) {
169+
throw new \InvalidArgumentException(sprintf(
170+
'Color channel "%s" must be between 0 and 255, got %d.',
171+
$channel,
172+
$value,
173+
));
174+
}
139175
}
140176
}

tests/lib/Avatar/UserAvatarTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@ public function testMixPalette(): void {
263263
$palette = Color::mixPalette($steps, $colorFrom, $colorTo);
264264
foreach ($palette as $j => $color) {
265265
// calc increment
266-
$incR = $colorTo->red() / $steps * $j;
267-
$incG = $colorTo->green() / $steps * $j;
268-
$incB = $colorTo->blue() / $steps * $j;
266+
$incR = (int)($colorTo->red() / $steps * $j);
267+
$incG = (int)($colorTo->green() / $steps * $j);
268+
$incB = (int)($colorTo->blue() / $steps * $j);
269269
// ensure everything is equal
270270
$this->assertEquals($color, new Color($incR, $incG, $incB));
271271
}

0 commit comments

Comments
 (0)