color-util-helpers 1.0.4 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/ng-package.json +8 -0
- package/package.json +2 -15
- package/src/lib/assets/picture.webp +0 -0
- package/src/lib/color-conversion.service.spec.ts +54 -0
- package/src/lib/color-conversion.service.ts +35 -0
- package/src/lib/color-extractor.directive.spec.ts +49 -0
- package/src/lib/color-extractor.directive.ts +28 -0
- package/src/lib/color-grab.directive.ts +204 -0
- package/src/lib/color-lighten-darken.service.spec.ts +61 -0
- package/src/lib/color-lighten-darken.service.ts +83 -0
- package/src/lib/color-pallette.service.spec.ts +85 -0
- package/src/lib/color-pallette.service.ts +191 -0
- package/src/lib/color-scheme.service.ts +123 -0
- package/src/lib/color-utilities-demo/color-utilities-demo.component.css +12 -0
- package/src/lib/color-utilities-demo/color-utilities-demo.component.html +109 -0
- package/src/lib/color-utilities-demo/color-utilities-demo.component.ts +57 -0
- package/src/lib/color-utils.module.ts +27 -0
- package/src/lib/text-color.service.spec.ts +75 -0
- package/src/lib/text-color.service.ts +101 -0
- package/{public-api.d.ts → src/public-api.ts} +7 -0
- package/tsconfig.lib.json +32 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
- package/color-util-helpers-1.0.4.tgz +0 -0
- package/esm2022/color-util-helpers.mjs +0 -5
- package/esm2022/lib/color-conversion.service.mjs +0 -39
- package/esm2022/lib/color-extractor.directive.mjs +0 -37
- package/esm2022/lib/color-lighten-darken.service.mjs +0 -79
- package/esm2022/lib/color-pallette.service.mjs +0 -172
- package/esm2022/lib/color-scheme.service.mjs +0 -113
- package/esm2022/lib/color-utilities-demo/color-utilities-demo.component.mjs +0 -41
- package/esm2022/lib/color-utils.module.mjs +0 -32
- package/esm2022/lib/text-color.service.mjs +0 -79
- package/esm2022/public-api.mjs +0 -12
- package/fesm2022/color-util-helpers.mjs +0 -580
- package/fesm2022/color-util-helpers.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/color-conversion.service.d.ts +0 -8
- package/lib/color-extractor.directive.d.ts +0 -13
- package/lib/color-lighten-darken.service.d.ts +0 -10
- package/lib/color-pallette.service.d.ts +0 -36
- package/lib/color-scheme.service.d.ts +0 -45
- package/lib/color-utilities-demo/color-utilities-demo.component.d.ts +0 -34
- package/lib/color-utils.module.d.ts +0 -10
- package/lib/text-color.service.d.ts +0 -18
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ This lib contains a variety of very useful color utils
|
|
|
8
8
|
- Text Color - provides the color to use (you provide light and dark) based on color provided
|
|
9
9
|
- Color Lighten Darken - Takes a color and you can darken or lighten
|
|
10
10
|
- Generate Random Color - This function generates a random hue value between 0 and 360 degrees, and random saturation and lightness values between 50% and 100%.
|
|
11
|
+
- Color Grabber - Directive when applied to a div will get the average color of the provided url of an image and then change the background color.
|
|
11
12
|
|
|
12
13
|
## Demo
|
|
13
14
|
|
|
@@ -32,6 +33,10 @@ npm install color-util-helpers
|
|
|
32
33
|
|
|
33
34
|
## Color Pallette Service and Directive
|
|
34
35
|
|
|
36
|
+
### Usage
|
|
37
|
+
|
|
38
|
+
Because this is a Directive - import the `ColorExtractorDirective`
|
|
39
|
+
|
|
35
40
|
define image path
|
|
36
41
|
|
|
37
42
|
this.colorSelectionService.getColorsFromImage('../assets/sample2.jpg')
|
|
@@ -123,3 +128,45 @@ This function takes a color '#3498db' and lightens the color by 20% and also dar
|
|
|
123
128
|
console.log(lighterColor, darkerColor);
|
|
124
129
|
```
|
|
125
130
|
|
|
131
|
+
# Color Grabber
|
|
132
|
+
|
|
133
|
+
This Angular Directive when applied to a div will get the average color of the provided url of an image and then change the background color to that color. Any test inside the div will be colored white or black based on the average color of the image that best suites it.
|
|
134
|
+
|
|
135
|
+
## Usage
|
|
136
|
+
|
|
137
|
+
Because this is a Directive - import the `ColorUtilitiesModule`
|
|
138
|
+
In the following example, the mat-card container has the `colorGrab` directive applied.
|
|
139
|
+
|
|
140
|
+
The `[imageUrl]="image"` is the image to grab the color from.
|
|
141
|
+
|
|
142
|
+
The `[light]="'#FFFFFF'"` is the light color to use if the average color is dark.
|
|
143
|
+
The `[dark]="'#000000'"` is the dark color to use if the average color is light.
|
|
144
|
+
|
|
145
|
+
The mat-card contaner background color will change to this color
|
|
146
|
+
|
|
147
|
+
The Text inside this container will also change ether to black or white depending what suites best based on that colors image.
|
|
148
|
+
|
|
149
|
+
To use in the directive, use the following
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
image = "https://picsum.photos/id/1/5616/3744"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
<mat-card style="margin: 8px;"
|
|
157
|
+
colorGrab [imageUrl]="image" [light]="'rgb(220, 220, 220)'" [dark]="'rgb(47, 79, 79)'"
|
|
158
|
+
class="box"
|
|
159
|
+
>
|
|
160
|
+
<h3>{{ item.author }}</h3>
|
|
161
|
+
<img [src]="image" width="160" height="120" style="object-fit: cover;">
|
|
162
|
+
</mat-card>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Paramiter
|
|
166
|
+
|
|
167
|
+
> imageUrl - provide the full URL of the image (png, jpg)
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
colorGrab [imageUrl]="item.download_url"
|
|
171
|
+
```
|
|
172
|
+
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "color-util-helpers",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": "^15.2.0",
|
|
6
6
|
"@angular/core": "^15.2.0"
|
|
@@ -8,18 +8,5 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"tslib": "^2.3.0"
|
|
10
10
|
},
|
|
11
|
-
"sideEffects": false
|
|
12
|
-
"module": "fesm2022/color-util-helpers.mjs",
|
|
13
|
-
"typings": "index.d.ts",
|
|
14
|
-
"exports": {
|
|
15
|
-
"./package.json": {
|
|
16
|
-
"default": "./package.json"
|
|
17
|
-
},
|
|
18
|
-
".": {
|
|
19
|
-
"types": "./index.d.ts",
|
|
20
|
-
"esm2022": "./esm2022/color-util-helpers.mjs",
|
|
21
|
-
"esm": "./esm2022/color-util-helpers.mjs",
|
|
22
|
-
"default": "./fesm2022/color-util-helpers.mjs"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
11
|
+
"sideEffects": false
|
|
25
12
|
}
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { ColorConversionService } from './color-conversion.service';
|
|
3
|
+
|
|
4
|
+
describe('ColorConversionService', () => {
|
|
5
|
+
let service: ColorConversionService;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
TestBed.configureTestingModule({});
|
|
9
|
+
service = TestBed.inject(ColorConversionService);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('rgbToHex', () => {
|
|
13
|
+
it('should convert RGB to hex', () => {
|
|
14
|
+
expect(service.rgbToHex([255, 0, 0])).toBe('#ff0000');
|
|
15
|
+
expect(service.rgbToHex([0, 255, 0])).toBe('#00ff00');
|
|
16
|
+
expect(service.rgbToHex([0, 0, 255])).toBe('#0000ff');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return empty string for null input', () => {
|
|
20
|
+
expect(service.rgbToHex(null)).toBe('');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return empty string for empty array input', () => {
|
|
24
|
+
expect(service.rgbToHex([])).toBe('');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle invalid RGB values', () => {
|
|
28
|
+
expect(service.rgbToHex([256, -1, 300])).toBe('');
|
|
29
|
+
expect(service.rgbToHex([255, 255])).toBe('');
|
|
30
|
+
expect(service.rgbToHex([255, 255, 255, 255])).toBe('');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('hexToRgb', () => {
|
|
35
|
+
it('should convert hex to RGB', () => {
|
|
36
|
+
expect(service.hexToRgb('#ff0000')).toEqual([255, 0, 0]);
|
|
37
|
+
expect(service.hexToRgb('#00ff00')).toEqual([0, 255, 0]);
|
|
38
|
+
expect(service.hexToRgb('#0000ff')).toEqual([0, 0, 255]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return empty array for null input', () => {
|
|
42
|
+
expect(service.hexToRgb(null)).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return empty array for undefined input', () => {
|
|
46
|
+
expect(service.hexToRgb(undefined)).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return empty array for invalid hex values', () => {
|
|
50
|
+
expect(service.hexToRgb('#zzz')).toEqual([]);
|
|
51
|
+
expect(service.hexToRgb('#12345')).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Injectable({
|
|
4
|
+
providedIn: 'root'
|
|
5
|
+
})
|
|
6
|
+
export class ColorConversionService {
|
|
7
|
+
|
|
8
|
+
rgbToHex(rgb: number[] | null): string {
|
|
9
|
+
if (rgb === null || rgb.length !== 3 || rgb.some(value => value < 0 || value > 255)) return '';
|
|
10
|
+
|
|
11
|
+
const [r, g, b] = rgb;
|
|
12
|
+
const hexR = this.componentToHex(r);
|
|
13
|
+
const hexG = this.componentToHex(g);
|
|
14
|
+
const hexB = this.componentToHex(b);
|
|
15
|
+
return "#" + hexR + hexG + hexB;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private componentToHex = (c: number) => {
|
|
19
|
+
const hex = c.toString(16);
|
|
20
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
hexToRgb(hex: string | null | undefined): number[] {
|
|
24
|
+
if (hex === null || hex === undefined) return [];
|
|
25
|
+
|
|
26
|
+
hex = (hex.length === 3) ? hex + hex : hex;
|
|
27
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
28
|
+
|
|
29
|
+
return result ? [
|
|
30
|
+
parseInt(result[1], 16),
|
|
31
|
+
parseInt(result[2], 16),
|
|
32
|
+
parseInt(result[3], 16)
|
|
33
|
+
] : [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ColorExtractorDirective } from './color-extractor.directive';
|
|
2
|
+
import { Component, DebugElement } from '@angular/core';
|
|
3
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
4
|
+
import { By } from '@angular/platform-browser';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
template: `<div getColor (colorValue)="onColorChange($event)" style="background-color: red;"></div>`
|
|
8
|
+
})
|
|
9
|
+
class TestHostComponent {
|
|
10
|
+
|
|
11
|
+
color = ''
|
|
12
|
+
|
|
13
|
+
onColorChange(color: string) {
|
|
14
|
+
this.color = color;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('ColorExtractorDirective', () => {
|
|
19
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
20
|
+
let divEl: DebugElement;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
TestBed.configureTestingModule({
|
|
24
|
+
declarations: [ColorExtractorDirective, TestHostComponent]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
28
|
+
divEl = fixture.debugElement.query(By.directive(ColorExtractorDirective));
|
|
29
|
+
fixture.detectChanges();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should create an instance', () => {
|
|
33
|
+
expect(divEl).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should emit background color on mouse enter', () => {
|
|
37
|
+
spyOn(fixture.componentInstance, 'onColorChange');
|
|
38
|
+
divEl.triggerEventHandler('mouseenter', null);
|
|
39
|
+
fixture.detectChanges();
|
|
40
|
+
expect(fixture.componentInstance.onColorChange).toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should emit white color on mouse leave', () => {
|
|
44
|
+
spyOn(fixture.componentInstance, 'onColorChange');
|
|
45
|
+
divEl.triggerEventHandler('mouseleave', null);
|
|
46
|
+
fixture.detectChanges();
|
|
47
|
+
expect(fixture.componentInstance.onColorChange).toHaveBeenCalledWith('white');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Directive, ElementRef, Renderer2, HostListener, EventEmitter, Output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[getColor]'
|
|
5
|
+
})
|
|
6
|
+
export class ColorExtractorDirective {
|
|
7
|
+
|
|
8
|
+
@Output() colorValue: EventEmitter<string> = new EventEmitter<string>();
|
|
9
|
+
|
|
10
|
+
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
|
|
11
|
+
|
|
12
|
+
get currentElement() {
|
|
13
|
+
return window.getComputedStyle(this.elementRef.nativeElement);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@HostListener('mouseenter')
|
|
17
|
+
onMouseEnter() {
|
|
18
|
+
// console.log('ENTER', this.currentElement)
|
|
19
|
+
this.colorValue.emit(this.currentElement.getPropertyValue('background-color'))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@HostListener('mouseleave')
|
|
23
|
+
onMouseLeave() {
|
|
24
|
+
// console.log('LEAVE', this.currentElement.getPropertyValue('background-color'))
|
|
25
|
+
this.colorValue.emit('white')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Directive({
|
|
4
|
+
selector: '[colorGrabber]'
|
|
5
|
+
})
|
|
6
|
+
export class ColorGrabberDirective implements OnInit {
|
|
7
|
+
|
|
8
|
+
ctx?: CanvasRenderingContext2D
|
|
9
|
+
|
|
10
|
+
@Input() imageUrl?: string
|
|
11
|
+
@Input() light?: string
|
|
12
|
+
@Input() dark?: string
|
|
13
|
+
|
|
14
|
+
constructor(private el: ElementRef) { }
|
|
15
|
+
|
|
16
|
+
ngOnInit() {
|
|
17
|
+
|
|
18
|
+
const canvas = document.createElement('canvas');
|
|
19
|
+
canvas.width = 1;
|
|
20
|
+
canvas.height = 1;
|
|
21
|
+
|
|
22
|
+
this.ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
|
23
|
+
|
|
24
|
+
const img = new Image();
|
|
25
|
+
img.src = this.imageUrl || '';
|
|
26
|
+
img.setAttribute('crossOrigin', '');
|
|
27
|
+
|
|
28
|
+
img.onload = () => {
|
|
29
|
+
this.ctx?.drawImage(img, 0, 0, 1, 1);
|
|
30
|
+
const imageData = this.ctx?.getImageData(0, 0, 1, 1);
|
|
31
|
+
|
|
32
|
+
if (imageData && imageData.data) {
|
|
33
|
+
const i = imageData.data;
|
|
34
|
+
|
|
35
|
+
const rgbColor = `rgba(${i[0]},${i[1]},${i[2]},${i[3]})`;
|
|
36
|
+
const hexColor = "#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1);
|
|
37
|
+
|
|
38
|
+
const textColor = this.textColorBasedOnBgColor(hexColor, this.light, this.dark);
|
|
39
|
+
|
|
40
|
+
const hsv = this.RGB2HSV({ r: i[0], g: i[1], b: i[2] });
|
|
41
|
+
hsv.hue = this.HueShift(hsv.hue, 135.0);
|
|
42
|
+
|
|
43
|
+
const secondaryColor = this.HSV2RGB(hsv);
|
|
44
|
+
const highlightColor = this.lightenDarkenColor(secondaryColor.hex, 50);
|
|
45
|
+
|
|
46
|
+
this.el.nativeElement.style.backgroundColor = rgbColor;
|
|
47
|
+
this.el.nativeElement.style.color = textColor;
|
|
48
|
+
} else {
|
|
49
|
+
console.error("Failed to get image data.");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
textColorBasedOnBgColor(
|
|
55
|
+
bgColor: string,
|
|
56
|
+
lightColor: string = '#FFFFFF',
|
|
57
|
+
darkColor: string = '#000000'
|
|
58
|
+
) {
|
|
59
|
+
|
|
60
|
+
const color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor
|
|
61
|
+
|
|
62
|
+
const r = parseInt(color.substring(0, 2), 16) // hexToR
|
|
63
|
+
const g = parseInt(color.substring(2, 4), 16) // hexToG
|
|
64
|
+
const b = parseInt(color.substring(4, 6), 16) // hexToB
|
|
65
|
+
|
|
66
|
+
const uicolors = [r / 255, g / 255, b / 255]
|
|
67
|
+
|
|
68
|
+
const c = uicolors.map((col) => {
|
|
69
|
+
|
|
70
|
+
if (col <= 0.03928) return col / 12.92
|
|
71
|
+
return Math.pow((col + 0.055) / 1.055, 2.4)
|
|
72
|
+
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2])
|
|
76
|
+
|
|
77
|
+
return (L > 0.179) ? darkColor : lightColor
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
RGB2HSV(rgb: { r: any, g: any, b: any}) {
|
|
82
|
+
|
|
83
|
+
const hsv = { saturation:0, hue:0, value: 0 }
|
|
84
|
+
|
|
85
|
+
const max = this.max3(rgb.r,rgb.g,rgb.b)
|
|
86
|
+
const dif = max - this.min3(rgb.r,rgb.g,rgb.b)
|
|
87
|
+
hsv.saturation = (max==0.0)?0:(100*dif/max)
|
|
88
|
+
|
|
89
|
+
if (hsv.saturation == 0) {
|
|
90
|
+
hsv.hue=0
|
|
91
|
+
} else if (rgb.r == max) {
|
|
92
|
+
hsv.hue=60.0*(rgb.g-rgb.b)/dif
|
|
93
|
+
} else if (rgb.g == max) {
|
|
94
|
+
hsv.hue=120.0+60.0*(rgb.b-rgb.r)/dif
|
|
95
|
+
} else if (rgb.b == max) {
|
|
96
|
+
hsv.hue=240.0+60.0*(rgb.r-rgb.g)/dif
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (hsv.hue<0.0) hsv.hue+=360.0
|
|
100
|
+
|
|
101
|
+
hsv.value = Math.round(max*100/255)
|
|
102
|
+
hsv.hue = Math.round(hsv.hue)
|
|
103
|
+
hsv.saturation = Math.round(hsv.saturation)
|
|
104
|
+
|
|
105
|
+
return hsv
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
HSV2RGB(hsv: any) {
|
|
109
|
+
|
|
110
|
+
const rgb = { r: 0, g: 0, b: 0}
|
|
111
|
+
|
|
112
|
+
if (hsv.saturation==0) {
|
|
113
|
+
rgb.r = rgb.g = rgb.b = Math.round(hsv.value*2.55)
|
|
114
|
+
} else {
|
|
115
|
+
|
|
116
|
+
hsv.hue/=60
|
|
117
|
+
hsv.saturation/=100
|
|
118
|
+
hsv.value/=100
|
|
119
|
+
const i = Math.floor(hsv.hue)
|
|
120
|
+
const f = hsv.hue-i
|
|
121
|
+
|
|
122
|
+
const p = hsv.value*(1-hsv.saturation)
|
|
123
|
+
const q = hsv.value*(1-hsv.saturation*f)
|
|
124
|
+
const t = hsv.value*(1-hsv.saturation*(1-f))
|
|
125
|
+
|
|
126
|
+
switch(i) {
|
|
127
|
+
|
|
128
|
+
case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break
|
|
129
|
+
case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break
|
|
130
|
+
case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break
|
|
131
|
+
case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break
|
|
132
|
+
case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break
|
|
133
|
+
|
|
134
|
+
default: rgb.r=hsv.value; rgb.g=p; rgb.b=q
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
rgb.r = Math.round(rgb.r*255)
|
|
139
|
+
rgb.g = Math.round(rgb.g*255)
|
|
140
|
+
rgb.b = Math.round(rgb.b*255)
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const rgbColor = `rgba(${rgb.r},${rgb.g},${rgb.b},${1})`
|
|
145
|
+
const hexColor = "#" + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1)
|
|
146
|
+
|
|
147
|
+
return { rgb: rgbColor, hex: hexColor }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
HueShift(h: any, s: any) {
|
|
151
|
+
h += s
|
|
152
|
+
while (h>=360.0) h-=360.0
|
|
153
|
+
while (h<0.0) h+=360.0
|
|
154
|
+
return h
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
min3(a: any, b: any, c: any) {
|
|
158
|
+
return (a<b)?((a<c)?a:c):((b<c)?b:c)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
max3(a: any, b: any, c: any) {
|
|
162
|
+
return (a>b)?((a>c)?a:c):((b>c)?b:c)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lightenDarkenColor(colorCode: any, amount: number) {
|
|
166
|
+
|
|
167
|
+
var usePound = false;
|
|
168
|
+
|
|
169
|
+
if (colorCode[0] == "#") {
|
|
170
|
+
colorCode = colorCode.slice(1);
|
|
171
|
+
usePound = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var num = parseInt(colorCode, 16);
|
|
175
|
+
|
|
176
|
+
var r = (num >> 16) + amount;
|
|
177
|
+
|
|
178
|
+
if (r > 255) {
|
|
179
|
+
r = 255;
|
|
180
|
+
} else if (r < 0) {
|
|
181
|
+
r = 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
var b = ((num >> 8) & 0x00FF) + amount;
|
|
185
|
+
|
|
186
|
+
if (b > 255) {
|
|
187
|
+
b = 255;
|
|
188
|
+
} else if (b < 0) {
|
|
189
|
+
b = 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var g = (num & 0x0000FF) + amount;
|
|
193
|
+
|
|
194
|
+
if (g > 255) {
|
|
195
|
+
g = 255;
|
|
196
|
+
} else if (g < 0) {
|
|
197
|
+
g = 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16)
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { ColorLightenDarkenService } from './color-lighten-darken.service';
|
|
3
|
+
import { TextColorService } from './text-color.service';
|
|
4
|
+
|
|
5
|
+
describe('ColorLightenDarkenService', () => {
|
|
6
|
+
let service: ColorLightenDarkenService;
|
|
7
|
+
let textColorServiceSpy: jasmine.SpyObj<TextColorService>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
const spy = jasmine.createSpyObj('TextColorService', ['fixColor']);
|
|
11
|
+
|
|
12
|
+
TestBed.configureTestingModule({
|
|
13
|
+
providers: [
|
|
14
|
+
ColorLightenDarkenService,
|
|
15
|
+
{ provide: TextColorService, useValue: spy }
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
service = TestBed.inject(ColorLightenDarkenService);
|
|
19
|
+
textColorServiceSpy = TestBed.inject(TextColorService) as jasmine.SpyObj<TextColorService>;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should be created', () => {
|
|
23
|
+
expect(service).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should lighten color by 20%', () => {
|
|
27
|
+
// Mocking fixColor to return RGB values
|
|
28
|
+
textColorServiceSpy.fixColor.and.returnValue([52, 152, 219]);
|
|
29
|
+
|
|
30
|
+
const result = service.lighten('#3498db', 0.2);
|
|
31
|
+
expect(result).toMatch(/#[0-9A-Fa-f]{6}/); // Check if it's a valid hex color
|
|
32
|
+
// Here we would typically validate the exact color, but due to rounding, exact match might be complex.
|
|
33
|
+
// Instead, we could check if it's lighter by comparing HSL values, but that's beyond simple testing.
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should darken color by 20%', () => {
|
|
37
|
+
// Mocking fixColor to return RGB values
|
|
38
|
+
textColorServiceSpy.fixColor.and.returnValue([52, 152, 219]);
|
|
39
|
+
|
|
40
|
+
const result = service.darken('#3498db', 0.2);
|
|
41
|
+
expect(result).toMatch(/#[0-9A-Fa-f]{6}/); // Check if it's a valid hex color
|
|
42
|
+
// Similar to lighten, we would ideally check if it's darker, but exact match is complicated by rounding.
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should not exceed lightness value of 1', () => {
|
|
46
|
+
// Test with an already light color to ensure it doesn't go over 100%
|
|
47
|
+
textColorServiceSpy.fixColor.and.returnValue([255, 255, 255]); // White
|
|
48
|
+
|
|
49
|
+
const result = service.lighten('#ffffff', 0.5);
|
|
50
|
+
expect(result).toBe('#ffffff'); // Should remain at maximum lightness
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle negative amount in lighten to darken', () => {
|
|
54
|
+
// This tests if lighten with negative amount behaves like darken
|
|
55
|
+
textColorServiceSpy.fixColor.and.returnValue([52, 152, 219]);
|
|
56
|
+
|
|
57
|
+
const result = service.lighten('#3498db', -0.2);
|
|
58
|
+
const darkenResult = service.darken('#3498db', 0.2);
|
|
59
|
+
expect(result).toEqual(darkenResult);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { TextColorService } from './text-color.service';
|
|
3
|
+
|
|
4
|
+
@Injectable({
|
|
5
|
+
providedIn: 'root'
|
|
6
|
+
})
|
|
7
|
+
export class ColorLightenDarkenService {
|
|
8
|
+
|
|
9
|
+
colors = inject(TextColorService)
|
|
10
|
+
|
|
11
|
+
// const color = '#3498db'; // Your color
|
|
12
|
+
// const lighterColor = lighten(color, 0.2); // 20% lighter
|
|
13
|
+
// const darkerColor = darken(color, 0.2); // 20% darker
|
|
14
|
+
|
|
15
|
+
// console.log(lighterColor, darkerColor);
|
|
16
|
+
|
|
17
|
+
constructor() { }
|
|
18
|
+
|
|
19
|
+
lighten(color: string, amount: number) {
|
|
20
|
+
|
|
21
|
+
const rgb = this.colors.fixColor(color)
|
|
22
|
+
// const rgb = color.match(/\w\w/g)?.map((x) => parseInt(x, 16)) || [];
|
|
23
|
+
|
|
24
|
+
// Convert RGB to HSL
|
|
25
|
+
let [r, g, b] = rgb.map((c) => c / 255);
|
|
26
|
+
const max = Math.max(r, g, b),
|
|
27
|
+
min = Math.min(r, g, b);
|
|
28
|
+
let h = 0,
|
|
29
|
+
s = 0,
|
|
30
|
+
l = (max + min) / 2;
|
|
31
|
+
|
|
32
|
+
if (max !== min) {
|
|
33
|
+
const d = max - min;
|
|
34
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
35
|
+
switch (max) {
|
|
36
|
+
case r:
|
|
37
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
38
|
+
break;
|
|
39
|
+
case g:
|
|
40
|
+
h = (b - r) / d + 2;
|
|
41
|
+
break;
|
|
42
|
+
case b:
|
|
43
|
+
h = (r - g) / d + 4;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
h /= 6;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Modify the lightness and clamp it to [0, 1]
|
|
50
|
+
l = Math.min(1, l + amount);
|
|
51
|
+
|
|
52
|
+
// Convert HSL back to RGB
|
|
53
|
+
if (s === 0) {
|
|
54
|
+
r = g = b = l; // achromatic
|
|
55
|
+
} else {
|
|
56
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
57
|
+
if (t < 0) t += 1;
|
|
58
|
+
if (t > 1) t -= 1;
|
|
59
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
60
|
+
if (t < 1 / 2) return q;
|
|
61
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
62
|
+
return p;
|
|
63
|
+
};
|
|
64
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
65
|
+
const p = 2 * l - q;
|
|
66
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
67
|
+
g = hue2rgb(p, q, h);
|
|
68
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Convert RGB back to hexadecimal color
|
|
72
|
+
const toHex = (x: number) =>
|
|
73
|
+
Math.round(x * 255)
|
|
74
|
+
.toString(16)
|
|
75
|
+
.padStart(2, '0');
|
|
76
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
darken(color: string, amount: number) {
|
|
80
|
+
return this.lighten(color, -amount);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|