mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
metafile: draw GDI+ percentage hatch styles
This commit is contained in:
1 parent
d6ed8c20f1
commit
21d7c1267e
3 files changed
+64
-10
No files matched your search
@@ -1295,13 +1295,35 @@ impl Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
/// An 8×8 hatch tile (HS_HORIZONTAL, HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGONAL, HS_CROSS,
|
||||
/// HS_DIAGCROSS): one-pixel lines in `fg` over `bg`, or over nothing when the background is
|
||||
/// transparent. None for an unknown style.
|
||||
/// An 8×8 GDI hatch tile (HS_HORIZONTAL through HS_DIAGCROSS). None for an unknown GDI style.
|
||||
pub(crate) fn hatch_tile(style: u32, fg: [u8; 4], bg: Option<[u8; 4]>) -> Option<Vec<u8>> {
|
||||
if style > 5 {
|
||||
hatch_tile_inner(style, fg, bg, false)
|
||||
}
|
||||
|
||||
/// An 8×8 GDI+ tile for the six basic and twelve percentage HatchStyle values. Percentage styles
|
||||
/// use a Bayer tile with the specified foreground coverage. Other GDI+ styles still use the
|
||||
/// fallback in `emfplus::Painter::fill` until their patterns have been mapped.
|
||||
pub(crate) fn emfplus_hatch_tile(style: u32, fg: [u8; 4], bg: Option<[u8; 4]>) -> Option<Vec<u8>> {
|
||||
hatch_tile_inner(style, fg, bg, true)
|
||||
}
|
||||
|
||||
fn hatch_tile_inner(style: u32, fg: [u8; 4], bg: Option<[u8; 4]>, gdi_plus: bool) -> Option<Vec<u8>> {
|
||||
if style > if gdi_plus { 17 } else { 5 } {
|
||||
return None;
|
||||
}
|
||||
// An 8×8 Bayer matrix keeps percentage hatches evenly distributed in a small, repeatable
|
||||
// tile. The twelve values are GDI+'s Percent05 through Percent90 styles.
|
||||
const BAYER: [[u8; 8]; 8] = [
|
||||
[0, 48, 12, 60, 3, 51, 15, 63],
|
||||
[32, 16, 44, 28, 35, 19, 47, 31],
|
||||
[8, 56, 4, 52, 11, 59, 7, 55],
|
||||
[40, 24, 36, 20, 43, 27, 39, 23],
|
||||
[2, 50, 14, 62, 1, 49, 13, 61],
|
||||
[34, 18, 46, 30, 33, 17, 45, 29],
|
||||
[10, 58, 6, 54, 9, 57, 5, 53],
|
||||
[42, 26, 38, 22, 41, 25, 37, 21],
|
||||
];
|
||||
const PERCENT: [u32; 12] = [5, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90];
|
||||
let mut px = Vec::with_capacity(8 * 8 * 4);
|
||||
for y in 0..8u32 {
|
||||
for x in 0..8u32 {
|
||||
@@ -1312,7 +1334,12 @@ pub(crate) fn hatch_tile(style: u32, fg: [u8; 4], bg: Option<[u8; 4]>) -> Option
|
||||
2 => f,
|
||||
3 => b,
|
||||
4 => h || v,
|
||||
_ => f || b,
|
||||
5 => f || b,
|
||||
6..=17 if gdi_plus => {
|
||||
let count = (PERCENT[(style - 6) as usize] * 64 + 50) / 100;
|
||||
u32::from(BAYER[y as usize][x as usize]) < count
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
px.extend(if on { fg } else { bg.unwrap_or([0, 0, 0, 0]) });
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::bytes::Bytes;
|
||||
use crate::canvas::{BS_NULL, BS_SOLID, Brush, Canvas, LogFont, PS_NULL, Pen, RawTile, Xform, hatch_tile, mul};
|
||||
use crate::canvas::{BS_NULL, BS_SOLID, Brush, Canvas, LogFont, PS_NULL, Pen, RawTile, Xform, emfplus_hatch_tile, mul};
|
||||
use crate::canvas::{Raw, RawClip};
|
||||
use crate::cliprect::{MAX_AREA_CLIP_WORK, clip_polygon_within, convex, flatten, ops_bbox, pts_bbox, rect_poly};
|
||||
use crate::dib::Rgba;
|
||||
@@ -845,9 +845,11 @@ impl Painter {
|
||||
cv.set_brush(Brush::new(BS_SOLID, *c));
|
||||
cv.shape(ops.to_vec(), true, false);
|
||||
}
|
||||
Fill::Hatch { style, fg, bg } => match hatch_tile(*style, *fg, Some(*bg)) {
|
||||
Fill::Hatch { style, fg, bg } => match emfplus_hatch_tile(*style, *fg, Some(*bg)) {
|
||||
Some(px) => cv.tile_shape(ops, Arc::new(RawTile { w: 8, h: 8, px, cell: cv.pattern_px })),
|
||||
// GDI+ has many more hatch styles than GDI: those fill with the blend of both colours.
|
||||
// Percentage styles are tiled above. The remaining GDI+ hatch styles have no tile
|
||||
// mapping yet, so keep the existing blended-colour fallback instead of inventing
|
||||
// a pattern that could disagree with the file.
|
||||
None => {
|
||||
cv.set_brush(Brush::new(BS_SOLID, lerp(*fg, *bg, 0.5)));
|
||||
cv.shape(ops.to_vec(), true, false);
|
||||
|
||||
@@ -2237,10 +2237,35 @@ fn emfplus_linear_gradient_fills_with_a_shaded_tile() {
|
||||
|
||||
#[test]
|
||||
fn emfplus_hatch_brush_fills_with_an_8x8_tile() {
|
||||
let brush = cat(vec![h32(0xDBC0_1002), h32(1), h32(4), h32(0xFF00_0000), h32(0xFFFF_FFFF)]);
|
||||
// GDI+ HatchStyle.Percent50 (12), as used by System.Drawing's HatchBrush.
|
||||
let brush = cat(vec![h32(0xDBC0_1002), h32(1), h32(12), h32(0xFF00_0000), h32(0xFFFF_FFFF)]);
|
||||
let fill = prec(0x400A, 0, &cat(vec![h32(0), h32(1), f32s(&[0.0, 0.0, 16.0, 16.0])]));
|
||||
let p = plus_pic(vec![prec(0x4008, 0x0100, &brush), fill]);
|
||||
assert!(matches!(&p.items[0], Item::Pattern { tile, .. } if tile.width == 8 && tile.height == 8));
|
||||
let tile = pattern_of(&p.items[0]);
|
||||
assert_eq!((tile.width, tile.height), (8, 8));
|
||||
let foreground = tile.rgba.as_chunks::<4>().0.iter().filter(|px| **px == [0, 0, 0, 255]).count();
|
||||
assert_eq!(foreground, 32, "Percent50 uses equal foreground and background coverage");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gdi_plus_percentage_hatch_styles_keep_the_documented_coverage() {
|
||||
const PERCENT: [usize; 12] = [5, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90];
|
||||
let fg = [0, 0, 0, 255];
|
||||
let bg = [255, 255, 255, 255];
|
||||
for style in 0..=17 {
|
||||
let tile = crate::canvas::emfplus_hatch_tile(style, fg, Some(bg)).expect("supported GDI+ hatch style");
|
||||
let foreground = tile.as_chunks::<4>().0.iter().filter(|px| **px == fg).count();
|
||||
assert!((1..64).contains(&foreground), "style {style} should mix both colours");
|
||||
if (6..=17).contains(&style) {
|
||||
let percent = PERCENT[(style - 6) as usize];
|
||||
assert_eq!(foreground, (percent * 64 + 50) / 100, "percentage style {style}");
|
||||
}
|
||||
}
|
||||
for style in 18..=52 {
|
||||
assert!(crate::canvas::emfplus_hatch_tile(style, fg, Some(bg)).is_none(), "style {style} has no confirmed tile mapping");
|
||||
}
|
||||
assert!(crate::canvas::emfplus_hatch_tile(53, fg, Some(bg)).is_none(), "out-of-range values stay unsupported");
|
||||
assert!(crate::canvas::hatch_tile(9, fg, Some(bg)).is_none(), "GDI's six-style range is unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user