mod: ядро (12) вместо синусоид-червячков -- снежинки

This commit is contained in:
2026-09-05 23:23:42 +03:00
parent 461f343b7a
commit c1a44143d9
4 changed files with 24 additions and 10 deletions
+1
View File
@@ -15,6 +15,7 @@ class HalftoneGenerateForm(forms.Form):
("star", "Звезда"), ("star", "Звезда"),
("cross", "Крестик"), ("cross", "Крестик"),
("line", "Линия"), ("line", "Линия"),
("snowflake", "Снежинка"),
("wave", "Волна"), ("wave", "Волна"),
("heart", "Сердечко"), ("heart", "Сердечко"),
] ]
+12 -4
View File
@@ -76,9 +76,17 @@ def generate_shape_def(shape: str, radius: int, shape_id: str) -> str:
th = max(1, round(r * 0.25)) th = max(1, round(r * 0.25))
return f'<rect id="{shape_id}" x="{-r}" y="{-th}" width="{2*r}" height="{2*th}" rx="1"/>' return f'<rect id="{shape_id}" x="{-r}" y="{-th}" width="{2*r}" height="{2*th}" rx="1"/>'
case "wave": case "snowflake":
th = max(1.0, r * 0.3) points = []
return f'<path id="{shape_id}" d="M{-r},0 Q{-r/2:.1f},{-r} 0,0 T{r},0" fill="none" stroke="currentColor" stroke-width="{th:.1f}"/>' inner_r = max(0.5, r * 0.25)
for i in range(16):
angle = i * math.pi / 8 - math.pi / 2
curr_r = r if i % 2 == 0 else inner_r
px = round(curr_r * math.cos(angle), 1)
py = round(curr_r * math.sin(angle), 1)
points.append(f"{px},{py}")
pts_str = " ".join(points)
return f'<polygon id="{shape_id}" points="{pts_str}"/>'
case "heart": case "heart":
r_top = round(r * 0.3) r_top = round(r * 0.3)
@@ -114,7 +122,7 @@ def generate_halftone_svg(
- image: PIL Image, файловый объект (BytesIO), байты или путь к файлу - image: PIL Image, файловый объект (BytesIO), байты или путь к файлу
- cols: число точек по наибольшей стороне сетки (10-200) - cols: число точек по наибольшей стороне сетки (10-200)
- max_radius: максимальный радиус/размер точки (3-20) - max_radius: максимальный радиус/размер точки (3-20)
- shape: тип фигуры ('circle', 'ring', 'square', 'diamond', 'triangle', 'hexagon', 'star', 'cross', 'line', 'wave', 'heart') - shape: тип фигуры ('circle', 'ring', 'square', 'diamond', 'triangle', 'hexagon', 'star', 'cross', 'line', 'snowflake', 'heart')
- color: основной HEX цвет (например '#a855ff') - color: основной HEX цвет (например '#a855ff')
- opacity: коэффициент непрозрачности (0.0 - 1.0) - opacity: коэффициент непрозрачности (0.0 - 1.0)
- blink: интенсивность мерцания (0 - выключено/статичный, 1-10 - скорость) - blink: интенсивность мерцания (0 - выключено/статичный, 1-10 - скорость)
+6 -1
View File
@@ -78,7 +78,7 @@ class HalftoneServiceTests(BaseMediaTestCase):
self.assertIn("<desc>Generated by Hypn0", svg) self.assertIn("<desc>Generated by Hypn0", svg)
def test_generate_shapes(self): def test_generate_shapes(self):
shapes = ["circle", "ring", "square", "diamond", "triangle", "hexagon", "star", "cross", "line", "wave", "heart"] shapes = ["circle", "ring", "square", "diamond", "triangle", "hexagon", "star", "cross", "line", "snowflake", "heart"]
for shape in shapes: for shape in shapes:
svg = generate_halftone_svg(self.img, cols=20, max_radius=5, shape=shape) svg = generate_halftone_svg(self.img, cols=20, max_radius=5, shape=shape)
self.assertIn("<svg", svg) self.assertIn("<svg", svg)
@@ -95,6 +95,11 @@ class HalftoneServiceTests(BaseMediaTestCase):
self.assertIn('<path id="s', heart_svg) self.assertIn('<path id="s', heart_svg)
self.assertNotIn('transform="scale', heart_svg) self.assertNotIn('transform="scale', heart_svg)
# Проверяем структуру снежинки: 8-лучевой полигон с точками
snowflake_svg = generate_halftone_svg(self.img, cols=20, max_radius=8, shape="snowflake")
self.assertIn('<polygon id="s', snowflake_svg)
self.assertIn('points="', snowflake_svg)
def test_generate_static_blink_zero(self): def test_generate_static_blink_zero(self):
svg = generate_halftone_svg(self.img, cols=20, blink=0) svg = generate_halftone_svg(self.img, cols=20, blink=0)
self.assertIn("animation:none", svg) self.assertIn("animation:none", svg)
+5 -5
View File
@@ -250,12 +250,12 @@
</label> </label>
<!-- 11. Волна / Синусоида --> <!-- 11. Снежинка / Звездочка -->
<label class="cursor-pointer group" title="Волна"> <label class="cursor-pointer group" title="Снежинка">
<input type="radio" name="shape" value="wave" class="hidden peer"> <input type="radio" name="shape" value="snowflake" class="hidden peer">
<div class="aspect-square flex flex-col items-center justify-center p-2 border-2 border-stone-100 dark:border-zinc-800 rounded-xl text-stone-400 group-hover:text-emerald-500 peer-checked:text-emerald-500 peer-checked:border-emerald-500 peer-checked:bg-emerald-50/10 transition-all hover:border-stone-300 dark:hover:border-zinc-700"> <div class="aspect-square flex flex-col items-center justify-center p-2 border-2 border-stone-100 dark:border-zinc-800 rounded-xl text-stone-400 group-hover:text-emerald-500 peer-checked:text-emerald-500 peer-checked:border-emerald-500 peer-checked:bg-emerald-50/10 transition-all hover:border-stone-300 dark:hover:border-zinc-700">
<svg class="w-6 h-6 stroke-current transition-colors" fill="none" stroke-width="2.5" stroke-linecap="round" viewBox="0 0 24 24"> <svg class="w-6 h-6 fill-current transition-colors" viewBox="0 0 24 24">
<path d="M2 12c2.5-4 5.5-4 8 0s5.5 4 8 0 3.5-4 4-4"/> <polygon points="12,2 13.2,9.5 19.1,4.9 14.5,10.8 22,12 14.5,13.2 19.1,19.1 13.2,14.5 12,22 10.8,14.5 4.9,19.1 9.5,13.2 2,12 9.5,10.8 4.9,4.9 10.8,9.5"/>
</svg> </svg>
</div> </div>
</label> </label>