Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
color.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Author:
4 * Martin Owens <doctormo@geek-2.com>
5 *
6 * Copyright (C) 2023 AUTHORS
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include "colors/color.h"
12
13#include <cassert>
14#include <cmath>
15#include <utility>
16
17#include "colors/manager.h"
18#include "spaces/base.h"
19#include "spaces/components.h"
20
21namespace Inkscape::Colors {
22namespace {
23
24template <typename T>
25static decltype(auto) assert_nonnull(T &&t)
26{
27 assert(t);
28 return std::forward<T>(t);
29}
30
31} // namespace
32
40Color::Color(Space::Type space_type, std::vector<double> values)
41 : Color(assert_nonnull(Manager::get().find(space_type)), std::move(values))
42{}
43
47Color::Color(uint32_t rgba, bool opacity)
48 : Color(Space::Type::RGB, rgba_to_values(rgba, opacity))
49{}
50
60Color::Color(std::shared_ptr<Space::AnySpace> space, std::vector<double> colors)
61 : _values(std::move(colors))
62 , _space(std::move(space))
63{
64 assert(_space->isValidData(_values));
65}
66
73bool Color::operator==(Color const &other) const
74{
75 // TODO: Adjust epsilon value to ignore roundtrip conversion rounding errors,
76 // but still keep high precision of color comparision.
77 //
78 // I arrived at this value empirically. If it is too big, then sometimes changes (by the user) are not picked up,
79 // and no refresh happens. If it is too small, then roundtrip conversions will also trigger changes, while they ideally should not.
80 // It's all a bit fragile.
81 return _space == other._space && _isnear(other._values, 0.00001);
82}
83
87double Color::get(unsigned index) const
88{
89 assert(index <= getOpacityChannel());
90 if (index < _values.size()) {
91 return _values[index];
92 } else {
93 return 1.0;
94 }
95}
96
106std::string Color::toString(bool opacity) const
107{
108 return _space->toString(_values, opacity);
109}
110
117uint32_t Color::toRGBA(double opacity) const
118{
119 return _space->toRGBA(_values, opacity);
120}
121
125uint32_t Color::toARGB(double opacity) const
126{
127 auto value = toRGBA(opacity);
128 return (value >> 8) | ((value & 0xff) << 24);
129}
130
134uint32_t Color::toABGR(double opacity) const
135{
136 auto value = toRGBA(opacity);
137 return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24);
138}
139
145bool Color::convert(Color const &other)
146{
147 if (convert(other._space)) {
148 enableOpacity(other.hasOpacity());
149 return true;
150 }
151 return false;
152}
153
159bool Color::convert(std::shared_ptr<Space::AnySpace> to_space)
160{
161 if (!to_space || !to_space->isValid()) {
162 return false;
163 }
164
165 if (_space != to_space) {
166 _space->convert(_values, to_space);
167 _space = std::move(to_space);
168 assert(_space->isValidData(_values));
169 }
170 _name = "";
171
172 return true;
173}
174
178bool Color::convert(Space::Type type)
179{
180 if (auto space = Manager::get().find(type)) {
181 return convert(space);
182 }
183 return false;
184}
185
189std::optional<Color> Color::converted(Color const &other) const
190{
191 Color copy = *this;
192 if (copy.convert(other)) {
193 return copy;
194 }
195 return {};
196}
197
201std::optional<Color> Color::converted(std::shared_ptr<Space::AnySpace> to_space) const
202{
203 Color copy = *this;
204 if (copy.convert(std::move(to_space))) {
205 return copy;
206 }
207 return {};
208}
209
215std::optional<Color> Color::converted(Space::Type type) const
216{
217 Color copy = *this;
218 if (copy.convert(type)) {
219 return copy;
220 }
221 return {};
222}
223
230void Color::setValues(std::vector<double> values)
231{
232 _name = "";
233 _values = std::move(values);
234 assert(_space->isValidData(_values));
235}
236
249bool Color::set(Color const &other, bool keep_space)
250{
251 if (keep_space) {
252 auto prev_space = _space;
253 auto prev_values = _values;
254 bool prev_opacity = hasOpacity();
255
256 if (set(other, false)) {
257 // Convert back to the previous space if needed.
258 convert(prev_space);
259 enableOpacity(prev_opacity);
260 // Return true if the converted result is different
261 return !_isnear(prev_values);
262 }
263 } else if (*this != other) {
264 _space = other._space;
265 _values = other._values;
266 _name = other._name;
267 return true;
268 }
269 return false;
270}
271
281bool Color::set(std::string const &parsable, bool keep_space)
282{
283 if (auto color = Color::parse(parsable)) {
284 return set(*color, keep_space);
285 }
286 return false;
287}
288
292bool Color::_isnear(std::vector<double> const &other, double epsilon) const
293{
294 bool is_near = _values.size() == other.size();
295 for (size_t i = 0; is_near && i < _values.size(); i++) {
296 is_near &= std::abs(_values[i] - other[i]) < epsilon;
297 }
298 return is_near;
299}
300
304std::optional<Color> Color::parse(char const *value)
305{
306 if (!value) {
307 return {};
308 }
309 return parse(std::string(value));
310}
311
315std::optional<Color> Color::parse(std::string const &value)
316{
317 Space::Type space_type;
318 std::string cms_name;
319 std::vector<double> values;
320 std::vector<double> fallback;
321 if (Parsers::get().parse(value, space_type, cms_name, values, fallback)) {
322 return ifValid(space_type, std::move(values));
323 }
324 // Couldn't be parsed as a color at all
325 return {};
326}
327
331std::optional<Color> Color::ifValid(Space::Type space_type, std::vector<double> values)
332{
333 if (auto space = Manager::get().find(space_type)) {
334 if (space->isValidData(values)) {
335 return std::make_optional<Color>(std::move(space), std::move(values));
336 }
337 }
338 // Invalid color data, return empty optional
339 return {};
340}
341
350bool Color::set(unsigned int index, double value)
351{
352 assert(index <= getOpacityChannel());
353 if (index == _values.size()) {
354 _values.push_back(1.0);
355 }
356 auto const changed = std::abs(_values[index] - value) >= 0.001;
357 _values[index] = value;
358 return changed;
359}
360
369bool Color::set(uint32_t rgba, bool opacity)
370{
371 if (_space->getType() != Space::Type::RGB) {
372 // Ensure we are in RGB
373 _space = assert_nonnull(Manager::get().find(Space::Type::RGB));
374 } else if (rgba == toRGBA(opacity)) {
375 return false; // nothing to do.
376 }
377 _name = "";
378 _values = std::move(rgba_to_values(rgba, opacity));
379 return true;
380}
381
385void Color::enableOpacity(bool enable)
386{
387 auto const has_opacity = hasOpacity();
388 if (enable && !has_opacity) {
389 _values.push_back(1.0);
390 } else if (!enable && has_opacity) {
391 _values.pop_back();
392 }
393}
394
398bool Color::hasOpacity() const
399{
400 return _values.size() > getOpacityChannel();
401}
402
407double Color::getOpacity() const
408{
409 return hasOpacity() ? _values.back() : 1.0;
410}
411
417double Color::stealOpacity()
418{
419 auto ret = getOpacity();
420 enableOpacity(false);
421 return ret;
422}
423
427unsigned int Color::getOpacityChannel() const
428{
429 return _space->getComponentCount();
430}
431
436unsigned int Color::getPin(unsigned int channel) const
437{
438 return 1 << channel;
439}
440
444bool Color::setOpacity(double opacity)
445{
446 if (hasOpacity()) {
447 if (opacity == _values.back()) {
448 return false;
449 }
450 _values.back() = opacity;
451 } else {
452 _values.emplace_back(opacity);
453 }
454 return true;
455}
456
460void Color::normalize()
461{
462 for (auto const &comp : _space->getComponents(hasOpacity())) {
463 _values[comp.index] = comp.normalize(_values[comp.index]);
464 }
465}
466
470Color Color::normalized() const
471{
472 Color copy = *this;
473 copy.normalize();
474 return copy;
475}
476
483void Color::invert(unsigned int pin)
484{
485 for (unsigned int i = 0; i < _values.size(); i++) {
486 if (pin & (1 << i)) {
487 continue;
488 }
489 _values[i] = 1.0 - _values[i];
490 }
491}
492
499void Color::jitter(double force, unsigned int pin)
500{
501 for (unsigned int i = 0; i < _values.size(); i++) {
502 if (pin & (1 << i)) {
503 continue;
504 }
505 // Random number between -0.5 and 0.5 times the force.
506 double r = (static_cast<double>(std::rand()) / RAND_MAX - 0.5);
507 _values[i] += r * force;
508 }
509 normalize();
510}
511
517void Color::compose(Color const &other)
518{
519 auto alpha = other.getOpacity();
521 [alpha](auto &value, auto otherValue) { value = value * (1.0 - alpha) + otherValue * alpha; });
522 setOpacity(1.0 - (1.0 - getOpacity()) * (1.0 - alpha));
523}
524
530Color Color::composed(Color const &other) const
531{
532 Color copy = *this;
533 copy.compose(other);
534 return copy;
535}
536
537/*
538 * Modify this color to be the average between two colors, modifying the first.
539 *
540 * @arg other - The other color to average with
541 * @arg pos - The position (i.e. t) between the two colors.
542 * @pin pin - Bit field, which channels should not change (see invert)
543 */
544void Color::average(Color const &other, double pos, unsigned int pin)
545{
546 _color_mutate_inplace(other, pin,
547 [pos](auto &value, auto otherValue) { value = value * (1 - pos) + otherValue * pos; });
548}
549
556Color Color::averaged(Color const &other, double pos) const
557{
558 Color copy = *this;
559 copy.average(other, pos);
560 return copy;
561}
562
566double Color::difference(Color const &other) const
567{
568 double ret = 0.0;
569 if (auto copy = other.converted(*this)) {
570 for (unsigned int i = 0; i < _values.size(); i++) {
571 ret += std::pow(_values[i] - (*copy)[i], 2);
572 }
573 }
574 return ret;
575}
576
583bool Color::isClose(Color const &other, double epsilon) const
584{
585 bool match = _space == other._space && _values.size() == other._values.size();
586 for (unsigned int i = 0; match && i < _values.size(); i++) {
587 match &= (std::fabs(_values[i] - other._values[i]) < epsilon);
588 }
589 return match;
590}
591
599bool Color::isSimilar(Color const &other, double epsilon) const
600{
601 if (other._space != _space) {
602 if (auto copy = other.converted(_space)) {
603 return isClose(*copy, epsilon);
604 }
605 return false; // bad color conversion
606 }
607 return isClose(other, epsilon);
608}
609
615bool Color::isOutOfGamut(std::shared_ptr<Space::AnySpace> other) const
616{
617 return _space->outOfGamut(_values, other);
618}
619
623bool Color::isOverInked() const
624{
625 return _space->overInk(_values);
626}
627
628// Color-color in-place modification template
629template <typename Func>
630void Color::_color_mutate_inplace(Color const &other, unsigned int pin, Func func)
631{
632 // Convert the other's space and opacity if it's different
633 if (other._space != _space || other.hasOpacity() != hasOpacity()) {
634 if (auto copy = other.converted(*this)) {
635 return _color_mutate_inplace(*copy, pin, func);
636 }
637 return; // Bad conversion
638 }
639
640 // Both are good, so average each channel
641 for (unsigned int i = 0; i < _values.size(); i++) {
642 if (pin & (1 << i)) {
643 continue;
644 }
645 func(_values[i], other[i]);
646 }
647}
648
649}; // namespace Inkscape::Colors
650
651/*
652 Local Variables:
653 mode:c++
654 c-file-style:"stroustrup"
655 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
656 indent-tabs-mode:nil
657 fill-column:99
658 End:
659*/
660// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
bool convert(Color const &other)
Convert to the same format as the other color.
Definition color.cpp:145
static std::optional< Color > ifValid(Space::Type space_type, std::vector< double > values)
Construct a color from the space type and values, if the values are valid.
Definition color.cpp:331
bool _isnear(std::vector< double > const &other, double epsilon=0.001) const
Returns true if the values are near to the other values.
Definition color.cpp:292
std::string _name
Definition color.h:98
unsigned int getOpacityChannel() const
Get the opacity channel index.
Definition color.cpp:427
void _color_mutate_inplace(Color const &other, unsigned int pin, Func avgFunc)
Definition color.cpp:630
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:117
bool setOpacity(double opacity)
Set the opacity of this color object.
Definition color.cpp:444
bool isClose(Color const &other, double epsilon=EPSILON) const
Find out if a color is a close match to another color of the same type.
Definition color.cpp:583
void enableOpacity(bool enabled)
Enables or disables the opacity channel.
Definition color.cpp:385
std::optional< Color > converted(Color const &other) const
Return a copy of this color converted to the same format as the other color.
Definition color.cpp:189
void normalize()
Make sure the values for this color are within acceptable ranges.
Definition color.cpp:460
std::vector< double > _values
Definition color.h:99
unsigned int getPin(unsigned int channel) const
Return the pin number (pow2) of the channel index to pin that channel in a mutation.
Definition color.cpp:436
std::shared_ptr< Space::AnySpace > _space
Definition color.h:100
static std::optional< Color > parse(char const *value)
Create an optional color if value is valid.
Definition color.cpp:304
bool hasOpacity() const
Returns true if there is an opacity channel in this color.
Definition color.cpp:398
double getOpacity() const
Get the opacity in this color, if it's stored.
Definition color.cpp:407
static Manager & get()
Definition manager.h:36
static Parsers & get()
Definition parser.h:102
A set of useful color modifying functions which do not fit as generic methods on the color class itse...
Definition profile.cpp:24
std::vector< double > rgba_to_values(uint32_t rgba, bool opacity)
Convert a 32bit unsigned int into a set of 3 or 4 double values for rgba.
Definition utils.cpp:54
STL namespace.
int index