CSS Цветове

Предадени решения

Краен срок:
27.10.2022 17:00
Точки:
15

Срокът за предаване на решения е отминал

use solution::*;
use std::panic::catch_unwind;
#[test]
fn test_rgb_display() {
assert_eq!(Color::new_rgb(0, 0, 0).to_string(), String::from("#000000"));
assert_eq!(Color::new_rgb(1, 20, 123).to_string(), String::from("#01147b"));
assert_eq!(Color::new_rgb(255, 255, 255).to_string(), String::from("#ffffff"));
}
#[test]
fn test_hsv_display() {
assert_eq!(Color::new_hsv(0, 0, 0).to_string(), String::from("hsv(0,0%,0%)"));
assert_eq!(Color::new_hsv(90, 3, 99).to_string(), String::from("hsv(90,3%,99%)"));
assert_eq!(Color::new_hsv(360, 100, 100).to_string(), String::from("hsv(360,100%,100%)"));
}
#[test]
fn test_new_hsv() {
assert!(catch_unwind(|| Color::new_hsv(361, 0, 0)).is_err());
assert!(catch_unwind(|| Color::new_hsv(100, 200, 0)).is_err());
assert!(catch_unwind(|| Color::new_hsv(200, 100, 255)).is_err());
}
#[test]
fn test_invert_rgb() {
let black = Color::new_rgb(0, 0, 0);
let white = Color::new_rgb(255, 255, 255);
assert_eq!(black.invert().unwrap_rgb(), white.unwrap_rgb());
assert_eq!(white.invert().unwrap_rgb(), black.unwrap_rgb());
let color1 = Color::new_rgb(120, 90, 135);
let color2 = Color::new_rgb(135, 165, 120);
assert_eq!(color1.invert().unwrap_rgb(), color2.unwrap_rgb());
assert_eq!(color2.invert().unwrap_rgb(), color1.unwrap_rgb());
assert_eq!(color1.invert().invert().unwrap_rgb(), color1.unwrap_rgb());
assert_eq!(color2.invert().invert().unwrap_rgb(), color2.unwrap_rgb());
}
#[test]
fn test_invert_hsv() {
let zero = Color::new_hsv(0, 0, 0);
let full = Color::new_hsv(360, 100, 100);
assert_eq!(zero.invert().unwrap_hsv(), full.unwrap_hsv());
assert_eq!(full.invert().unwrap_hsv(), zero.unwrap_hsv());
let color1 = Color::new_hsv(120, 90, 35);
let color2 = Color::new_hsv(240, 10, 65);
assert_eq!(color1.invert().unwrap_hsv(), color2.unwrap_hsv());
assert_eq!(color2.invert().unwrap_hsv(), color1.unwrap_hsv());
assert_eq!(color1.invert().invert().unwrap_hsv(), color1.unwrap_hsv());
assert_eq!(color2.invert().invert().unwrap_hsv(), color2.unwrap_hsv());
}

Някои студенти си казват "аз не ща да уча математика, аз ще пиша уеб". След това прекарват няколко часа в опити да центрират div-ове и им се ще да бяха си учили диференциалните уравнения и да ходят в CERN примерно.

Добре де, днешно време не е чак толкова трудно... но все пак може да попишем малко математика свързана с уеб-а, дори и да се свежда до събиране и изваждане на числа.

Нека да имплементираме някои методи на следния тип:

pub enum Color {
    RGB {
        red: u8,
        green: u8,
        blue: u8
    },
    HSV {
        hue: u16,
        saturation: u8,
        value: u8,
    }
}

Това е енумериран тип, който има два варианта:

  • RGB с полета за червено, зелено и синьо
  • HSV за hue, saturation и value

Първия сигурно е сравнително ясен -- стойностите на трите цветови канала вървят от 0 до 255 и смесването на цветовете ни дава целия спектър. Нули на всички канали ни дава черно, а 255 ще е чисто бяло. Ако сложим 255 на червено и зелено, ще получим ярко жълто.

Втория вариант е малко по-интересен -- първата стойност е градус и върви от 0 до 360. Това е стойност, която определя цвета спрямо позицията му на някакъв специфичен color wheel. Стойностите "saturation" и "value" определят яркостта и светлостта на този цвят, от 0% до 100%. Или нещо такова, цялото нещо е ужасно неинтуитивно: https://en.wikipedia.org/wiki/HSL_and_HSV. Тази анимация не обяснява почти нищо, но пък е доста готина: https://twitter.com/DanHollick/status/1578071950123057152

Като за първо домашно, няма да правим нещо твърде сложно с цветовете. Нека първо да си направим удобни конструктори:

impl Color {
    /// Конструира нова стойност от вариант `RGB` с дадените стойности за червено, зелено и синьо.
    ///
    pub fn new_rgb(red: u8, green: u8, blue: u8) -> Color {
        todo!()
    }

    /// Конструира нова стойност от вариант `HSV` с дадените стойности.
    ///
    /// В случай, че hue е над 360 или saturation или value са над 100, очакваме да `panic!`-нете с
    /// каквото съобщение си изберете.
    ///
    pub fn new_hsv(hue: u16, saturation: u8, value: u8) -> Color {
        todo!()
    }
}

Обърнете внимание на бележката за паниката. Обикновено ще се справяме с невалиден вход по други начини, но още не сме стигнали дотам, така че panic! в случай на невалиден вход ще ни свърши работа.

Впрочем, няма много смисъл да валидираме RGB входа -- ако подадете число над 255, това би било компилационна грешка. Nice.

За да ни е по-лесно да "извадим парчетата" на двата варианта, нека да имплементираме методи, които просто ни ги дават, стига да сме супер-дупер сигурни, че питаме за правилния вариант:

impl Color {
    /// Ако `self` е `RGB`, тогава връщате неговите `red`, `green`, `blue` компоненти в този ред.
    /// Иначе, `panic!`-вате с каквото съобщение си изберете.
    ///
    pub fn unwrap_rgb(&self) -> (u8, u8, u8) {
        todo!()
    }

    /// Ако `self` е `HSV`, тогава връщате неговите `hue`, `saturation`, `value` компоненти в този
    /// ред. Иначе, `panic!`-вате с каквото съобщение си изберете.
    ///
    pub fn unwrap_hsv(&self) -> (u16, u8, u8) {
        todo!()
    }
}

Забележете, че получавате self по reference. Това е смислено -- само четете тази стойност. Но ще трябва да връщате числата като стойности. Това също е логично, рядко са ви особено полезни указатели към числа.

Ако ще използваме тези структури за CSS, трябва да ги форматираме като за CSS. Нека да напечатаме цвета като нещо, което CSS ще разбере:

impl Color {
    /// В случай, че варианта на `self` е `RGB`, очакваме низ със съдържание `#rrggbb`, където
    /// червения, зеления и синия компонент са форматирани в шестнадесетична система, и всеки от тях е
    /// точно два символа с малки букви (запълнени с нули).
    ///
    /// Ако варианта е `HSV`, очакваме низ `hsv(h,s%,v%)`, където числата са си напечатани в
    /// десетичната система, без водещи нули, без интервали след запетаите, вторите две завършващи на
    /// `%`.
    ///
    pub fn to_string(&self) -> String {
        todo!()
    }
}

Примерно:

println!("{}", Color::new_rgb(0, 0, 0).to_string());     //=> #000000
println!("{}", Color::new_rgb(255, 1, 255).to_string()); //=> #ff01ff

println!("{}", Color::new_hsv(0, 0, 0).to_string());       //=> hsv(0,0%,0%)
println!("{}", Color::new_hsv(360, 100, 100).to_string()); //=> hsv(360,100%,100%)

Ако си мислите колко досадно ще е да форматирате числата в шестнадесетичен вид с padding от нули... може би някъде из слайдовете на лекциите ще намерите някакъв hint как да стане доста лесно. Може би ще намерите точно тези две неща използвани в някоя лекция.

Последното което искаме, е да напишете малко логика за инвертиране на два цвята. В случая на RGB, обратното на #00ff00 ще е #ff00ff, в случая на HSV, инвертиране на hsv(0,50%,0%) ще бъде hsv(360,50%,100%). Тоест, за всяка индивидуална компонента вземаме допълнението към максимума. Не сме сигурни дали това работи смислено за HSV, но сме програмисти, не сме... "цветови учени"?

impl Color {
    /// Инвертира цвят покомпонентно -- за всяка от стойностите се взема разликата с максимума.
    ///
    pub fn invert(&self) -> Self {
        todo!()
    }
}

Никоя от тези задачки не би трябвало да е кой знае колко сложна, но е първо домашно. Все пак има какво да се сбърка (винаги!), така че внимавайте и си проверявайте работата.

Задължително прочетете (или си припомнете): Указания за предаване на домашни

Погрижете се решението ви да се компилира с базовия тест:

use solution::*;
#[test]
fn test_basic() {
let color1 = Color::new_rgb(0, 0, 0);
assert_eq!(color1.unwrap_rgb().0, 0);
assert_eq!(&color1.to_string()[0..1], "#");
let color2 = Color::new_hsv(0, 0, 0);
assert_eq!(color2.unwrap_hsv().0, 0);
assert_eq!(color1.invert().unwrap_rgb().0, 255);
}