Compare commits

...

2 Commits

Author SHA1 Message Date
Aloxaf 319066e9a1 refactor: add TextLineDrawer trait 2024-03-04 10:28:57 +08:00
Aloxaf a460f0a152 fix: build on nightly 2024-03-04 10:28:12 +08:00
7 changed files with 137 additions and 98 deletions

77
Cargo.lock generated
View File

@ -680,9 +680,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.3"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown",
@ -726,12 +726,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libloading"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
"windows-targets 0.52.4",
]
[[package]]
@ -768,9 +768,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "malloc_buf"
@ -1017,8 +1017,7 @@ dependencies = [
[[package]]
name = "pathfinder_simd"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93"
source = "git+https://github.com/servo/pathfinder#cb4eb597c3fef886417e2ff7b89e8348a131b417"
dependencies = [
"rustc_version",
]
@ -1306,7 +1305,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.52",
]
[[package]]
@ -1414,9 +1413,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.51"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@ -1494,7 +1493,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
"syn 2.0.52",
]
[[package]]
@ -1578,9 +1577,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@ -1654,7 +1653,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.3",
"windows-targets 0.52.4",
]
[[package]]
@ -1674,17 +1673,17 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm 0.52.3",
"windows_aarch64_msvc 0.52.3",
"windows_i686_gnu 0.52.3",
"windows_i686_msvc 0.52.3",
"windows_x86_64_gnu 0.52.3",
"windows_x86_64_gnullvm 0.52.3",
"windows_x86_64_msvc 0.52.3",
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
]
[[package]]
@ -1695,9 +1694,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
@ -1707,9 +1706,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
@ -1719,9 +1718,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
@ -1731,9 +1730,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
@ -1743,9 +1742,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
@ -1755,9 +1754,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
@ -1767,9 +1766,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.3"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "wio"

View File

@ -63,3 +63,6 @@ pasteboard = "0.1.3"
[target.'cfg(target_os = "windows")'.dependencies]
clipboard-win = "5.2.0"
image = { version = "0.24", default-features = false, features = ["jpeg", "bmp", "jpeg_rayon"] }
[patch.crates-io]
pathfinder_simd = { version = "0.5.0", git = "https://github.com/servo/pathfinder" }

View File

@ -2,6 +2,7 @@ use anyhow::{Context, Error};
use clipboard::{ClipboardContext, ClipboardProvider};
use image::Rgba;
use silicon::directories::PROJECT_DIRS;
use silicon::font::FontCollection;
use silicon::formatter::{ImageFormatter, ImageFormatterBuilder};
use silicon::utils::{Background, ShadowAdder, ToRgba};
use std::ffi::OsString;
@ -269,7 +270,7 @@ impl Config {
}
}
pub fn get_formatter(&self) -> Result<ImageFormatter, Error> {
pub fn get_formatter(&self) -> Result<ImageFormatter<FontCollection>, Error> {
let formatter = ImageFormatterBuilder::new()
.line_pad(self.line_pad)
.window_controls(!self.no_window_controls)

View File

@ -148,6 +148,7 @@ fn run() -> Result<(), Error> {
let mut formatter = config.get_formatter()?;
let image = formatter.format(&highlight, &theme);
let image = DynamicImage::ImageRgba8(image);
if config.to_clipboard {
dump_image_to_clipboard(&image)?;

View File

@ -21,7 +21,7 @@ use font_kit::font::Font;
use font_kit::hinting::HintingOptions;
use font_kit::properties::{Properties, Style, Weight};
use font_kit::source::SystemSource;
use image::{GenericImage, Pixel};
use image::{GenericImage, Pixel, Rgba, RgbaImage};
use imageproc::definitions::Clamp;
use imageproc::pixelops::weighted_sum;
use pathfinder_geometry::transform2d::Transform2F;
@ -29,6 +29,46 @@ use std::collections::HashMap;
use std::sync::Arc;
use syntect::highlighting;
/// a single line text drawer
pub trait TextLineDrawer {
/// get the height of the text
fn height(&mut self, text: &str) -> u32;
/// get the width of the text
fn width(&mut self, text: &str) -> u32;
/// draw the text
fn draw_text(
&mut self,
image: &mut RgbaImage,
color: Rgba<u8>,
x: u32,
y: u32,
font_style: FontStyle,
text: &str,
);
}
impl TextLineDrawer for FontCollection {
fn height(&mut self, _text: &str) -> u32 {
self.get_font_height()
}
fn width(&mut self, text: &str) -> u32 {
self.layout(text, REGULAR).1
}
fn draw_text(
&mut self,
image: &mut RgbaImage,
color: Rgba<u8>,
x: u32,
y: u32,
font_style: FontStyle,
text: &str,
) {
self.draw_text_mut(image, color, x, y, font_style, text);
}
}
/// Font style
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum FontStyle {
@ -172,11 +212,15 @@ impl ImageFont {
///
/// It can be used to draw text on the image.
#[derive(Debug)]
pub struct FontCollection(Vec<ImageFont>);
pub struct FontCollection {
fonts: Vec<ImageFont>,
}
impl Default for FontCollection {
fn default() -> Self {
Self(vec![ImageFont::default()])
Self {
fonts: vec![ImageFont::default()],
}
}
}
@ -191,11 +235,11 @@ impl FontCollection {
Err(err) => eprintln!("[error] Error occurs when load font `{}`: {}", name, err),
}
}
Ok(Self(fonts))
Ok(Self { fonts })
}
fn glyph_for_char(&self, c: char, style: FontStyle) -> Option<(u32, &ImageFont, &Font)> {
for font in &self.0 {
for font in &self.fonts {
let result = font.get_by_style(style);
if let Some(id) = result.glyph_for_char(c) {
return Some((id, font, result));
@ -207,7 +251,7 @@ impl FontCollection {
/// get max height of all the fonts
pub fn get_font_height(&self) -> u32 {
self.0
self.fonts
.iter()
.map(|font| font.get_font_height())
.max()
@ -350,9 +394,9 @@ impl FontCollection {
I: GenericImage,
<I::Pixel as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
{
let metrics = self.0[0].get_regular().metrics();
let metrics = self.fonts[0].get_regular().metrics();
let offset =
(metrics.descent / metrics.units_per_em as f32 * self.0[0].size).round() as i32;
(metrics.descent / metrics.units_per_em as f32 * self.fonts[0].size).round() as i32;
let (glyphs, width) = self.layout(text, style);
@ -372,6 +416,7 @@ impl FontCollection {
}
}
#[derive(Debug)]
struct PositionedGlyph {
id: u32,
font: Font,

View File

@ -1,11 +1,11 @@
//! Format the output of syntect into an image
use crate::error::FontError;
use crate::font::{FontCollection, FontStyle};
use crate::font::{FontCollection, FontStyle, TextLineDrawer};
use crate::utils::*;
use image::{DynamicImage, GenericImageView, Rgba, RgbaImage};
use image::{Rgba, RgbaImage};
use syntect::highlighting::{Color, Style, Theme};
pub struct ImageFormatter {
pub struct ImageFormatter<T> {
/// pad between lines
/// Default: 2
line_pad: u32,
@ -42,7 +42,7 @@ pub struct ImageFormatter {
line_number_chars: u32,
/// font of english character, should be mono space font
/// Default: Hack (builtin)
font: FontCollection,
font: T,
/// Highlight lines
highlight_lines: Vec<u32>,
/// Shadow adder
@ -151,7 +151,7 @@ impl<S: AsRef<str> + Default> ImageFormatterBuilder<S> {
self
}
pub fn build(self) -> Result<ImageFormatter, FontError> {
pub fn build(self) -> Result<ImageFormatter<FontCollection>, FontError> {
let font = if self.font.is_empty() {
FontCollection::default()
} else {
@ -191,19 +191,19 @@ struct Drawable {
drawables: Vec<(u32, u32, Option<Color>, FontStyle, String)>,
}
impl ImageFormatter {
impl<T: TextLineDrawer> ImageFormatter<T> {
/// calculate the height of a line
fn get_line_height(&self) -> u32 {
self.font.get_font_height() + self.line_pad
fn get_line_height(&mut self) -> u32 {
self.font.height(" ") + self.line_pad
}
/// calculate the Y coordinate of a line
fn get_line_y(&self, lineno: u32) -> u32 {
fn get_line_y(&mut self, lineno: u32) -> u32 {
lineno * self.get_line_height() + self.code_pad + self.code_pad_top
}
/// calculate the size of code area
fn get_image_size(&self, max_width: u32, lineno: u32) -> (u32, u32) {
fn get_image_size(&mut self, max_width: u32, lineno: u32) -> (u32, u32) {
(
(max_width + self.code_pad).max(150),
self.get_line_y(lineno + 1) + self.code_pad,
@ -211,18 +211,18 @@ impl ImageFormatter {
}
/// Calculate where code start
fn get_left_pad(&self) -> u32 {
fn get_left_pad(&mut self) -> u32 {
self.code_pad
+ if self.line_number {
let tmp = format!("{:>width$}", 0, width = self.line_number_chars as usize);
2 * self.line_number_pad + self.font.get_text_len(&tmp)
2 * self.line_number_pad + self.font.width(&tmp)
} else {
0
}
}
/// create
fn create_drawables(&self, v: &[Vec<(Style, &str)>]) -> Drawable {
fn create_drawables(&mut self, v: &[Vec<(Style, &str)>]) -> Drawable {
// tab should be replaced to whitespace so that it can be rendered correctly
let tab = " ".repeat(self.tab_width as usize);
let mut drawables = vec![];
@ -246,7 +246,7 @@ impl ImageFormatter {
text.to_owned(),
));
width += self.font.get_text_len(&text);
width += self.font.width(&text);
max_width = max_width.max(width);
}
@ -255,7 +255,7 @@ impl ImageFormatter {
if self.window_title.is_some() {
let title = self.window_title.as_ref().unwrap();
let title_width = self.font.get_text_len(title);
let title_width = self.font.width(title);
let ctrls_offset = if self.window_controls {
self.window_controls_width + self.title_bar_pad
@ -266,7 +266,7 @@ impl ImageFormatter {
drawables.push((
ctrls_offset + self.title_bar_pad,
self.title_bar_pad + ctrls_center - self.font.get_font_height() / 2,
self.title_bar_pad + ctrls_center - self.font.height(" ") / 2,
None,
FontStyle::BOLD,
title.to_string(),
@ -283,7 +283,7 @@ impl ImageFormatter {
}
}
fn draw_line_number(&self, image: &mut DynamicImage, lineno: u32, mut color: Rgba<u8>) {
fn draw_line_number(&mut self, image: &mut RgbaImage, lineno: u32, mut color: Rgba<u8>) {
for i in color.0.iter_mut() {
*i = (*i).saturating_sub(20);
}
@ -293,36 +293,37 @@ impl ImageFormatter {
i + self.line_offset,
width = self.line_number_chars as usize
);
self.font.draw_text_mut(
let y = self.get_line_y(i);
self.font.draw_text(
image,
color,
self.code_pad,
self.get_line_y(i),
y,
FontStyle::REGULAR,
&line_number,
);
}
}
fn highlight_lines<I: IntoIterator<Item = u32>>(&self, image: &mut DynamicImage, lines: I) {
fn highlight_lines<I: IntoIterator<Item = u32>>(&mut self, image: &mut RgbaImage, lines: I) {
let width = image.width();
let height = self.font.get_font_height() + self.line_pad;
let mut color = image.get_pixel(20, 20);
let height = self.get_line_height();
let color = image.get_pixel_mut(20, 20);
for i in color.0.iter_mut() {
*i = (*i).saturating_add(40);
}
let shadow = RgbaImage::from_pixel(width, height, color);
let shadow = RgbaImage::from_pixel(width, height, *color);
for i in lines {
let y = self.get_line_y(i - 1);
copy_alpha(&shadow, image.as_mut_rgba8().unwrap(), 0, y);
copy_alpha(&shadow, image, 0, y);
}
}
// TODO: use &T instead of &mut T ?
pub fn format(&mut self, v: &[Vec<(Style, &str)>], theme: &Theme) -> DynamicImage {
pub fn format(&mut self, v: &[Vec<(Style, &str)>], theme: &Theme) -> RgbaImage {
if self.line_number {
self.line_number_chars =
(((v.len() + self.line_offset as usize) as f32).log10() + 1.0).floor() as u32;
@ -338,15 +339,15 @@ impl ImageFormatter {
let foreground = theme.settings.foreground.unwrap();
let background = theme.settings.background.unwrap();
let mut image =
DynamicImage::ImageRgba8(RgbaImage::from_pixel(size.0, size.1, background.to_rgba()));
let mut image = RgbaImage::from_pixel(size.0, size.1, background.to_rgba());
if !self.highlight_lines.is_empty() {
let highlight_lines = self
.highlight_lines
.iter()
.cloned()
.filter(|&n| n >= 1 && n <= drawables.max_lineno + 1);
.filter(|&n| n >= 1 && n <= drawables.max_lineno + 1)
.collect::<Vec<_>>();
self.highlight_lines(&mut image, highlight_lines);
}
if self.line_number {
@ -355,8 +356,7 @@ impl ImageFormatter {
for (x, y, color, style, text) in drawables.drawables {
let color = color.unwrap_or(foreground).to_rgba();
self.font
.draw_text_mut(&mut image, color, x, y, style, &text);
self.font.draw_text(&mut image, color, x, y, style, &text);
}
if self.window_controls {

View File

@ -1,7 +1,7 @@
use crate::error::ParseColorError;
use image::imageops::{crop_imm, resize, FilterType};
use image::Pixel;
use image::{DynamicImage, GenericImage, GenericImageView, Rgba, RgbaImage};
use image::{GenericImage, GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::{draw_filled_rect_mut, draw_line_segment_mut};
use imageproc::rect::Rect;
@ -74,17 +74,17 @@ pub struct WindowControlsParams {
}
/// Add the window controls for image
pub(crate) fn add_window_controls(image: &mut DynamicImage, params: &WindowControlsParams) {
pub(crate) fn add_window_controls(image: &mut RgbaImage, params: &WindowControlsParams) {
let color = [
("#FF5F56", "#E0443E"),
("#FFBD2E", "#DEA123"),
("#27C93F", "#1AAB29"),
];
let mut background = image.get_pixel(37, 37);
let background = image.get_pixel_mut(37, 37);
background.0[3] = 0;
let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, background);
let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, *background);
let step = (params.radius * 2) as i32;
let spacer = step * 2;
let center_y = (params.height / 2) as i32;
@ -112,12 +112,7 @@ pub(crate) fn add_window_controls(image: &mut DynamicImage, params: &WindowContr
FilterType::Triangle,
);
copy_alpha(
&title_bar,
image.as_mut_rgba8().unwrap(),
params.padding,
params.padding,
);
copy_alpha(&title_bar, image, params.padding, params.padding);
}
#[derive(Clone, Debug)]
@ -204,7 +199,7 @@ impl ShadowAdder {
self
}
pub fn apply_to(&self, image: &DynamicImage) -> DynamicImage {
pub fn apply_to(&self, image: &RgbaImage) -> RgbaImage {
// the size of the final image
let width = image.width() + self.pad_horiz * 2;
let height = image.height() + self.pad_vert * 2;
@ -226,14 +221,9 @@ impl ShadowAdder {
// shadow = blur(&shadow, self.blur_radius);
// copy the original image to the top of it
copy_alpha(
image.as_rgba8().unwrap(),
&mut shadow,
self.pad_horiz,
self.pad_vert,
);
copy_alpha(image, &mut shadow, self.pad_horiz, self.pad_vert);
DynamicImage::ImageRgba8(shadow)
shadow
}
}
@ -266,7 +256,7 @@ pub(crate) fn copy_alpha(src: &RgbaImage, dst: &mut RgbaImage, x: u32, y: u32) {
}
/// Round the corner of the image
pub(crate) fn round_corner(image: &mut DynamicImage, radius: u32) {
pub(crate) fn round_corner(image: &mut RgbaImage, radius: u32) {
// draw a circle with given foreground on given background
// then split it into four pieces and paste them to the four corner of the image
//
@ -287,7 +277,7 @@ pub(crate) fn round_corner(image: &mut DynamicImage, radius: u32) {
&mut circle,
(((radius + 1) * 2) as i32, ((radius + 1) * 2) as i32),
radius as i32 * 2,
foreground,
*foreground,
);
// scale down the circle to the correct size