pydrawcv 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. pydrawcv-0.1.0/LICENSE +21 -0
  2. pydrawcv-0.1.0/PKG-INFO +258 -0
  3. pydrawcv-0.1.0/README.md +232 -0
  4. pydrawcv-0.1.0/drawcv/__init__.py +247 -0
  5. pydrawcv-0.1.0/drawcv/animation/__init__.py +83 -0
  6. pydrawcv-0.1.0/drawcv/animation/easing.py +225 -0
  7. pydrawcv-0.1.0/drawcv/animation/interpolation.py +86 -0
  8. pydrawcv-0.1.0/drawcv/animation/timeline.py +94 -0
  9. pydrawcv-0.1.0/drawcv/animation/timing.py +157 -0
  10. pydrawcv-0.1.0/drawcv/animation/track.py +189 -0
  11. pydrawcv-0.1.0/drawcv/animation/video_renderer.py +146 -0
  12. pydrawcv-0.1.0/drawcv/canvas.py +72 -0
  13. pydrawcv-0.1.0/drawcv/core/__init__.py +34 -0
  14. pydrawcv-0.1.0/drawcv/core/bounds.py +151 -0
  15. pydrawcv-0.1.0/drawcv/core/color.py +200 -0
  16. pydrawcv-0.1.0/drawcv/core/drawable.py +532 -0
  17. pydrawcv-0.1.0/drawcv/core/enums.py +91 -0
  18. pydrawcv-0.1.0/drawcv/core/exceptions.py +42 -0
  19. pydrawcv-0.1.0/drawcv/core/geometry.py +247 -0
  20. pydrawcv-0.1.0/drawcv/core/geometry_utils.py +496 -0
  21. pydrawcv-0.1.0/drawcv/core/path_processing.py +488 -0
  22. pydrawcv-0.1.0/drawcv/core/transform.py +245 -0
  23. pydrawcv-0.1.0/drawcv/effects/__init__.py +31 -0
  24. pydrawcv-0.1.0/drawcv/effects/blur.py +76 -0
  25. pydrawcv-0.1.0/drawcv/effects/clipping.py +143 -0
  26. pydrawcv-0.1.0/drawcv/effects/effect.py +13 -0
  27. pydrawcv-0.1.0/drawcv/effects/mask.py +128 -0
  28. pydrawcv-0.1.0/drawcv/effects/shadow.py +90 -0
  29. pydrawcv-0.1.0/drawcv/group.py +446 -0
  30. pydrawcv-0.1.0/drawcv/history/__init__.py +26 -0
  31. pydrawcv-0.1.0/drawcv/history/command.py +61 -0
  32. pydrawcv-0.1.0/drawcv/history/commands.py +304 -0
  33. pydrawcv-0.1.0/drawcv/history/manager.py +134 -0
  34. pydrawcv-0.1.0/drawcv/layer.py +280 -0
  35. pydrawcv-0.1.0/drawcv/positioning.py +253 -0
  36. pydrawcv-0.1.0/drawcv/renderer.py +1157 -0
  37. pydrawcv-0.1.0/drawcv/scene.py +954 -0
  38. pydrawcv-0.1.0/drawcv/selection.py +284 -0
  39. pydrawcv-0.1.0/drawcv/serialization/__init__.py +25 -0
  40. pydrawcv-0.1.0/drawcv/serialization/json_decoder.py +36 -0
  41. pydrawcv-0.1.0/drawcv/serialization/json_encoder.py +47 -0
  42. pydrawcv-0.1.0/drawcv/serialization/registry.py +217 -0
  43. pydrawcv-0.1.0/drawcv/shapes/__init__.py +52 -0
  44. pydrawcv-0.1.0/drawcv/shapes/arc.py +287 -0
  45. pydrawcv-0.1.0/drawcv/shapes/arrow.py +317 -0
  46. pydrawcv-0.1.0/drawcv/shapes/bezier.py +246 -0
  47. pydrawcv-0.1.0/drawcv/shapes/circle.py +189 -0
  48. pydrawcv-0.1.0/drawcv/shapes/ellipse.py +203 -0
  49. pydrawcv-0.1.0/drawcv/shapes/freehand.py +556 -0
  50. pydrawcv-0.1.0/drawcv/shapes/image.py +350 -0
  51. pydrawcv-0.1.0/drawcv/shapes/line.py +180 -0
  52. pydrawcv-0.1.0/drawcv/shapes/path.py +536 -0
  53. pydrawcv-0.1.0/drawcv/shapes/polygon.py +174 -0
  54. pydrawcv-0.1.0/drawcv/shapes/polyline.py +219 -0
  55. pydrawcv-0.1.0/drawcv/shapes/rectangle.py +193 -0
  56. pydrawcv-0.1.0/drawcv/shapes/rounded_rectangle.py +235 -0
  57. pydrawcv-0.1.0/drawcv/shapes/text.py +357 -0
  58. pydrawcv-0.1.0/drawcv/styles/__init__.py +6 -0
  59. pydrawcv-0.1.0/drawcv/styles/fill.py +72 -0
  60. pydrawcv-0.1.0/drawcv/styles/stroke.py +101 -0
  61. pydrawcv-0.1.0/pydrawcv.egg-info/PKG-INFO +258 -0
  62. pydrawcv-0.1.0/pydrawcv.egg-info/SOURCES.txt +91 -0
  63. pydrawcv-0.1.0/pydrawcv.egg-info/dependency_links.txt +1 -0
  64. pydrawcv-0.1.0/pydrawcv.egg-info/requires.txt +5 -0
  65. pydrawcv-0.1.0/pydrawcv.egg-info/top_level.txt +1 -0
  66. pydrawcv-0.1.0/pyproject.toml +44 -0
  67. pydrawcv-0.1.0/setup.cfg +4 -0
  68. pydrawcv-0.1.0/tests/test_advanced_rendering.py +98 -0
  69. pydrawcv-0.1.0/tests/test_advanced_shapes.py +244 -0
  70. pydrawcv-0.1.0/tests/test_animation_and_video.py +329 -0
  71. pydrawcv-0.1.0/tests/test_bounds.py +82 -0
  72. pydrawcv-0.1.0/tests/test_color.py +94 -0
  73. pydrawcv-0.1.0/tests/test_compositing.py +487 -0
  74. pydrawcv-0.1.0/tests/test_fill_rules.py +53 -0
  75. pydrawcv-0.1.0/tests/test_freehand.py +525 -0
  76. pydrawcv-0.1.0/tests/test_geometry.py +84 -0
  77. pydrawcv-0.1.0/tests/test_geometry_utils.py +192 -0
  78. pydrawcv-0.1.0/tests/test_groups.py +216 -0
  79. pydrawcv-0.1.0/tests/test_hit_testing.py +111 -0
  80. pydrawcv-0.1.0/tests/test_interpolation.py +215 -0
  81. pydrawcv-0.1.0/tests/test_layers.py +132 -0
  82. pydrawcv-0.1.0/tests/test_lookup.py +127 -0
  83. pydrawcv-0.1.0/tests/test_manipulation.py +126 -0
  84. pydrawcv-0.1.0/tests/test_positioning.py +141 -0
  85. pydrawcv-0.1.0/tests/test_progressive_rendering.py +193 -0
  86. pydrawcv-0.1.0/tests/test_renderer.py +218 -0
  87. pydrawcv-0.1.0/tests/test_scene.py +119 -0
  88. pydrawcv-0.1.0/tests/test_selection.py +154 -0
  89. pydrawcv-0.1.0/tests/test_serialization.py +589 -0
  90. pydrawcv-0.1.0/tests/test_shapes.py +153 -0
  91. pydrawcv-0.1.0/tests/test_styles.py +76 -0
  92. pydrawcv-0.1.0/tests/test_transforms.py +60 -0
  93. pydrawcv-0.1.0/tests/test_undo_redo.py +334 -0
pydrawcv-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DrawCV Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydrawcv
3
+ Version: 0.1.0
4
+ Summary: A highly manipulable 2D retained-mode drawing library built on OpenCV and NumPy.
5
+ Author: DrawCV Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nuhyamin1/drawcv
8
+ Project-URL: Repository, https://github.com/nuhyamin1/drawcv
9
+ Project-URL: Issues, https://github.com/nuhyamin1/drawcv/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Multimedia :: Graphics
13
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Operating System :: OS Independent
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.26.0
22
+ Requires-Dist: opencv-python>=4.8.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # DrawCV
28
+
29
+ A clean, extensible, object-oriented 2D drawing engine and graphics library built on **OpenCV** and **NumPy** as the raster rendering backend.
30
+
31
+ Unlike immediate-mode OpenCV functions (`cv2.line`, `cv2.rectangle`, `cv2.circle`) that permanently alter pixels in-place, **DrawCV** operates on a **strict retained-mode graphics architecture**. Every drawn object remains addressable, editable, movable, transformable, hideable, reorderable, and re-renderable after creation.
32
+
33
+ ```
34
+ Drawable Entities ───► Scene Graph ───► OpenCVRenderer ───► Canvas Buffer
35
+ (Object Model) (Authoritative) (Centralized AA (NumPy / OpenCV)
36
+ & Compositing)
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Key Architectural Principles
42
+
43
+ 1. **Retained-Mode Graphics**: The `Scene` is authoritative. The raster image is purely disposable and reproducible upon re-rendering. Mutating an object's properties in place and invoking `.render()` regenerates the scene without manual pixel clearing or damage rect calculations.
44
+ 2. **Decoupled Primitives (Zero OpenCV in Shapes)**: `Line`, `Rectangle`, and `Circle` do not import or call `cv2`. All rasterization, OpenCV calls, coordinate quantization, and alpha compositing are centralized strictly inside `OpenCVRenderer`.
45
+ 3. **Immutable Value Objects**: `Point` is an immutable `@dataclass(frozen=True)` with vector arithmetic. `Color` and `BoundingBox` enforce strict validation (no silent clamping of invalid channel or dimension values).
46
+ 4. **Clean Geometry Ownership**: `Scene` owns logical dimensions (`width`, `height`) and `background`. `Canvas` owns the resulting BGR NumPy array.
47
+ 5. **Exact Alpha Compositing**: Translucent strokes and fills are blended mathematically:
48
+ $$\text{effective\_alpha} = \text{color.a} \times \text{style.opacity} \times \text{drawable.opacity}$$
49
+ $$\text{dst} = \text{src} \cdot \alpha + \text{dst} \cdot (1 - \alpha)$$
50
+ 6. **No Silent Unsupported Behavior**: Unimplemented transforms (e.g. arbitrary rotation of rectangles or non-uniform scaling) raise `RenderError` rather than silently failing to render.
51
+
52
+ ---
53
+
54
+ ## Installation
55
+
56
+ Install via pip from PyPI:
57
+
58
+ ```bash
59
+ pip install pydrawcv
60
+ ```
61
+
62
+ Or install from source for development:
63
+
64
+ ```bash
65
+ git clone https://github.com/nuhyamin1/drawcv.git
66
+ cd drawcv
67
+ pip install -e ".[dev]"
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Coordinate System
73
+
74
+ DrawCV uses standard computer graphics screen coordinates:
75
+
76
+ - **Origin `(0, 0)`**: Top-left corner of the canvas.
77
+ - **+X axis**: Extends horizontally to the right.
78
+ - **+Y axis**: Extends vertically downward.
79
+ - **Subpixel Precision**: Model geometry is stored as floating-point numbers (`Point(120.35, 440.81)`). The renderer is responsible for coordinate quantization and anti-aliasing during rasterization.
80
+
81
+ ---
82
+
83
+ ## Quickstart Example
84
+
85
+ ```python
86
+ from drawcv import (
87
+ Scene,
88
+ OpenCVRenderer,
89
+ Point,
90
+ Color,
91
+ Circle,
92
+ Line,
93
+ Rectangle,
94
+ StrokeStyle,
95
+ FillStyle,
96
+ )
97
+
98
+ # 1. Create a retained Scene
99
+ scene = Scene(width=1200, height=800, background=Color.white())
100
+
101
+ # 2. Add shapes
102
+ circle = Circle(
103
+ center=Point(400, 300),
104
+ radius=100,
105
+ stroke=StrokeStyle(color=Color.black(), width=4.0),
106
+ fill=FillStyle(color=Color.from_hex("#73B9FF"), opacity=0.5)
107
+ )
108
+
109
+ line = Line(
110
+ start=Point(100, 100),
111
+ end=Point(800, 500),
112
+ stroke=StrokeStyle(color=Color.red(), width=5.0)
113
+ )
114
+
115
+ rectangle = Rectangle(
116
+ position=Point(700, 150),
117
+ width=220,
118
+ height=150,
119
+ stroke=StrokeStyle(color=Color.black(), width=3.0),
120
+ fill=FillStyle(color=Color.green(), opacity=0.4)
121
+ )
122
+
123
+ scene.add(circle)
124
+ scene.add(line)
125
+ scene.add(rectangle)
126
+
127
+ # 3. Retained-mode in-place manipulation
128
+ circle.center = Point(500, 350)
129
+ circle.radius = 130
130
+ line.stroke.color = Color.blue()
131
+ rectangle.width = 280
132
+
133
+ # 4. Render to Canvas
134
+ renderer = OpenCVRenderer()
135
+ canvas = renderer.render(scene)
136
+
137
+ # 5. Export to disk
138
+ canvas.save("output.png")
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Core Components (Phase 1)
144
+
145
+ ### Geometry & Color
146
+ - `Point(x, y)`: Immutable 2D vector supporting `+`, `-`, `*`, `/`, `.translate(dx, dy)`, `.distance_to(other)`.
147
+ - `Color(r, g, b, a=1.0)`: RGBA color model with `.from_hex()`, `.from_rgb()`, `.from_bgr()`, `.to_bgr()`, `.to_hex()`, `.black()`, `.white()`, `.red()`, etc.
148
+ - `BoundingBox(x, y, width, height)`: Spatial bounding box with `left`, `right`, `top`, `bottom`, `center`, `corners`, `.contains()`, `.intersects()`, `.union()`, `.expand()`.
149
+
150
+ ### Styling
151
+ - `StrokeStyle(color, width, opacity, line_type)`: Anti-aliased stroke styling (`LineType.AA`, `LINE_8`, `LINE_4`).
152
+ - `FillStyle(enabled, color, opacity)`: Interior shape fill styling.
153
+
154
+ ### Shapes
155
+ - `Line(start, end, stroke)`: Computes `length`, `angle`, `center`, and stroke-aware bounds.
156
+ - `Rectangle(position, width, height, stroke, fill)`: Computes `area`, `center`, `corners`, and stroke-aware bounds.
157
+ - `Circle(center, radius, stroke, fill)`: Computes `diameter`, `area`, `circumference`, and stroke-aware bounds.
158
+
159
+ ### Advanced Geometry & Manipulation (Phases 2 & 3)
160
+ - **Transforms & Manipulation**: `.move(dx, dy)`, `.rotate(deg, pivot)`, `.scale(sx, sy, pivot)`, `.clone()`, exact geometric hit-testing.
161
+ - **Advanced Shapes**: `Ellipse`, `Arc` (OPEN, CHORD, PIE), `Polygon`, `Polyline`, `Arrow` (TRIANGLE, OPEN, DIAMOND, CIRCLE heads), `BezierCurve` (quadratic and cubic), `Path` (retained vector path with `MoveTo`, `LineTo`, `QuadraticTo`, `CubicTo`, `Close`), and `RoundedRectangle`.
162
+ - **Topological Fill Rules**: `FillRule.EVEN_ODD` and `FillRule.NON_ZERO` evaluation.
163
+
164
+ ### Scene System (Phase 4)
165
+ - **Hierarchical Groups (`Group`)**: Container drawables supporting nested groups, cascaded transforms, non-destructive coordinate propagation, zero-shift world-transform reparenting, and collective world AABB computation.
166
+ - **Rendering Layers (`Layer`)**: Global rendering passes and stack organization (`z_order`), visibility, opacity, and cascading locked state.
167
+ - **Logical Selection (`Selection`)**: Multi-object selection controller with ancestor/descendant normalization (preventing double-transforms), rigid rotation around collective bounds center, scaling, batch property adjustments, and container-safe `.group()`.
168
+ - **Declarative Relative Positioning**: `align_left`, `align_right`, `align_top`, `align_bottom`, `align_center_x`, `align_center_y`, `align_centers`, `place_above`, `place_below`, `place_left_of`, `place_right_of` (with default `gap=20.0`), `distribute_horizontally`, `distribute_vertically`.
169
+ - **Authoritative Scene Registry & Query**: Fast $O(1)$ lookup via `scene.get(id)` across layers and groups, collision prevention, `find_by_name`, `find_by_tag`, `find_by_type`, `find_by_metadata`, and predicate searches.
170
+
171
+ ### Freehand Engine (Phase 5)
172
+ - **`StrokePoint`**: Immutable frozen point holding coordinates with physical capture metadata (`pressure`, `timestamp`, `velocity`).
173
+ - **`FreehandStroke`**: Retained-mode freehand stroke retaining authoritative raw sampled input points without mutation.
174
+ - **Modular Path Processing Pipeline**:
175
+ - **RDP Simplification (`rdp_simplify`)**: Noise and redundant point reduction while preserving salient corners and all metadata.
176
+ - **Chaikin Smoothing (`chaikin_smooth`)**: Iterative corner-cutting curve refinement with open endpoint preservation.
177
+ - **Catmull-Rom Spline (`catmull_rom_spline`)**: $C^1$-continuous cubic spline passing exactly through control points with centripetal parameterization ($\alpha=0.5$).
178
+ - **Linear Metadata Interpolation**: Clamped linear interpolation for pressure $[0.0, 1.0]$ and timestamps preventing overshoot and preserving physical monotonicity.
179
+ - **Variable-Width Contour Rasterization**: Segment-normal tapered quadrilateral ribbons with circular joint/cap discs for stylus calligraphy (pressure-sensitive) and fountain pen flick dynamics (velocity-sensitive).
180
+
181
+ ### Compositing & Effects (Phase 6)
182
+ - **Isolated Offscreen Surfaces**: Eliminates overlapping-geometry opacity accumulation by rasterizing translucent groups and layers to isolated premultiplied float32 BGRA buffers.
183
+ - **Single-Application Opacity Boundary**: Ancestor opacity is withheld during isolated subtree rendering and applied strictly once at the compositing boundary.
184
+ - **Premultiplied Alpha Pipeline**: Mathematically rigorous blending ($C_{\text{out}} = C_s + C_d(1 - \alpha_s)$, $\alpha_{\text{out}} = \alpha_s + \alpha_d(1 - \alpha_s)$) with 4-channel synchronous scaling across B, G, R, and A to prevent color fringing.
185
+ - **Effects**:
186
+ - `BlurEffect`: Content blur (`BlurType.GAUSSIAN`, `BlurType.BOX`) executed on premultiplied intermediates without boundary halos.
187
+ - `ShadowEffect`: Cast shadows with signed asymmetric offsets (`offset_x`, `offset_y`), blur radius, and shadow color, rendered behind base geometry.
188
+ - **Effect-Inflated Buffers**: Asymmetric buffer expansion accommodating signed shadow offsets and blur penumbras.
189
+ - **Clipping & Masking**:
190
+ - `ClipRect` & `ClipPath`: Vector stencils applied as the final post-effects clipping boundary.
191
+ - `Mask`: Grayscale luminance/alpha masks with `MaskMapping.FIT_BOUNDS` mapped against pre-effect visual bounds.
192
+ - **New Retained Primitives**:
193
+ - `ImageObject`: Raster images supporting BGR and BGRA, source cropping, target sizing, affine warps, and alpha compositing.
194
+ - `Text`: Typography with font families (`FontFamily.SANS_SERIF`, `SERIF`, `MONOSPACE`, `SCRIPT`), alignments (`LEFT`, `CENTER`, `RIGHT`), and background plates with optional `background_radius`.
195
+
196
+ - **Phase 7 Capabilities (Persistence & History Engine)**:
197
+ - **Canonical JSON Document Envelope**: Root header (`{"format": "drawcv", "version": "1.0", "scene": ...}`) with bit-for-bit deterministic serialization (`sort_keys=True`, `allow_nan=False`).
198
+ - **Strict Version Migration Pipeline**: `SchemaMigrator` enables sequential forward migrations for backward compatibility across schema versions.
199
+ - **Structured Raster Payload**: Lossless PNG base64 encoding with decoded shape and `uint8` dtype validation for `Mask` and `ImageObject`.
200
+ - **Lossless Float Alpha**: Colors serialize alpha as exact floating-point numbers (`0.5 == 0.5`), preventing 8-bit quantization drift.
201
+ - **In-Place Live Object Identity Preservation**: History undo/redo mutates existing live Python instances via `_apply_semantic_state()`, preserving references (`scene.get(id) is original_obj`).
202
+ - **Recursive Group Semantic Snapshots**: `Group._get_semantic_state()` captures descendant states and ordering; `Group._apply_semantic_state()` restores states in-place.
203
+ - **Reversible Operations**: `add`, `remove`, `move_to_front`, `move_to_back`, `move_forward`, `move_backward`, `group`, `ungroup`, `move_object`, `rotate_object`, `scale_object`, `restyle_object`, `with scene.edit(...)`, and `with scene.batch(...)`.
204
+ - **History Invariants**: Automatic redo branch invalidation, no-op edit filtering, exception rollback, and drift-free 50-cycle undo/redo.
205
+
206
+ - **Phase 8 Capabilities (Temporal Drawing & Animation Engine)**:
207
+ - **Timing & Pacing Model**: Dedicated `Timing` value object controlling `start_time`, `duration`, `delay`, `speed`, and `loop`, with clamped `get_progress()` for progressive reveal and raw `evaluate()` for property tracks.
208
+ - **Easing Suite**: 22 standard easing curves across Linear, Quadratic, Cubic, Sine, Exponential, Circular, Elastic, and Bounce families.
209
+ - **Arc-Length Progressive Path Slicing**: True Euclidean path-length parameterization for `Line`, `Polyline`, `Arrow`, `BezierCurve`, `Path`, `FreehandStroke`, and `Arc` (sweep angle reveal).
210
+ - **Stroke-First Reveal Semantics**: While `render_progress < 1.0`, shapes progressively expose outline stroke only; interior fills are suppressed until reaching full completion ($p = 1.0$).
211
+ - **Explicit Progressive Capability**: `supports_progressive_rendering` prevents slicing recursion and ensures non-path shapes (e.g. `Rectangle`, `Circle`) render safely.
212
+ - **Observational Non-Destructive Sampling**: `scene.render_at_time(t)` evaluates timestamps with suspended history and unconditionally restores authored state in `finally`, preserving live object identity and preventing history pollution.
213
+ - **Automatic Timing Binding**: `drawable.timing` automatically drives `drawable.render_progress` during temporal evaluation without requiring explicit Timeline tracks.
214
+ - **Synchronized Timeline**: Multi-track property animations with deterministic insertion-order conflict resolution and comprehensive `scene.temporal_duration` derivation.
215
+ - **Typed Value Serialization**: `AnimationTrack` persists typed value codecs (`"number"`, `"point"`, `"color"`, `"transform"`, `"bounds"`) and string-only easing contracts.
216
+ - **Document Schema 1.1 & Migration**: Automatic forward migration from Phase 7 (`"1.0"`) to `"1.1"`, injecting temporal defaults across layers and nested groups.
217
+ - **Multi-Frame & Video Export**: `VideoRenderer` encoding playable MP4 video via OpenCV `cv2.VideoWriter`, image sequences, and optional Pillow animated GIF.
218
+
219
+ ---
220
+
221
+ ## Running Tests & Demos
222
+
223
+ Run automated unit tests (240 tests, 100% green):
224
+
225
+ ```bash
226
+ pytest tests/ -v
227
+ ```
228
+
229
+ Run visual demonstrations:
230
+
231
+ ```bash
232
+ python examples/phase1_demo.py
233
+ python examples/phase2_demo.py
234
+ python examples/phase3_demo.py
235
+ python examples/phase4_demo.py
236
+ python examples/phase5_demo.py
237
+ python examples/phase6_demo.py
238
+ python examples/phase7_demo.py
239
+ python examples/progressive_drawing.py
240
+ python examples/phase8_demo.py
241
+ ```
242
+
243
+ Generated outputs will be saved to `examples/output/`.
244
+
245
+ ---
246
+
247
+ ## Development Roadmap
248
+
249
+ - [x] **Phase 1 — Foundation**: Retained scene graph, Point, Color, BoundingBox, Line, Rectangle, Circle, StrokeStyle, FillStyle, OpenCVRenderer, Canvas, true alpha compositing, unit tests, visual demos.
250
+ - [x] **Phase 2 — Manipulation**: Full affine transform rendering (rotation, non-uniform scaling), object cloning, hit testing.
251
+ - [x] **Phase 3 — Advanced Geometry**: Ellipse, Arc, Polygon, Polyline, Arrow, Bézier curves, General Path, RoundedRectangle.
252
+ - [x] **Phase 4 — Scene System**: Semantic Groups, Rendering Layers, Selection models, Relative positioning utilities, Centralized scene registry.
253
+ - [x] **Phase 5 — Freehand Engine**: FreehandStroke, StrokePoint, Chaikin smoothing, Ramer-Douglas-Peucker simplification, Catmull-Rom interpolation, pressure and velocity sensitive variable width.
254
+ - [x] **Phase 6 — Compositing & Effects**: Grayscale masks, clipping rectangles, clipping paths, ImageObject, Text, blur, drop shadows, isolated group/layer offscreen compositing.
255
+ - [x] **Phase 7 — Persistence & History**: Strict canonical JSON serialization, forward schema migration, command-based Undo/Redo engine, live identity preservation, recursive group state snapshots.
256
+ - [x] **Phase 8 — Temporal Drawing & Animation**: Timing metadata, arc-length progressive rendering, stroke-first reveal, 22 easing curves, non-destructive temporal sampling, Timeline multi-track synchronization, schema 1.1 migration, OpenCV MP4 video encoding.
257
+
258
+
@@ -0,0 +1,232 @@
1
+ # DrawCV
2
+
3
+ A clean, extensible, object-oriented 2D drawing engine and graphics library built on **OpenCV** and **NumPy** as the raster rendering backend.
4
+
5
+ Unlike immediate-mode OpenCV functions (`cv2.line`, `cv2.rectangle`, `cv2.circle`) that permanently alter pixels in-place, **DrawCV** operates on a **strict retained-mode graphics architecture**. Every drawn object remains addressable, editable, movable, transformable, hideable, reorderable, and re-renderable after creation.
6
+
7
+ ```
8
+ Drawable Entities ───► Scene Graph ───► OpenCVRenderer ───► Canvas Buffer
9
+ (Object Model) (Authoritative) (Centralized AA (NumPy / OpenCV)
10
+ & Compositing)
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Key Architectural Principles
16
+
17
+ 1. **Retained-Mode Graphics**: The `Scene` is authoritative. The raster image is purely disposable and reproducible upon re-rendering. Mutating an object's properties in place and invoking `.render()` regenerates the scene without manual pixel clearing or damage rect calculations.
18
+ 2. **Decoupled Primitives (Zero OpenCV in Shapes)**: `Line`, `Rectangle`, and `Circle` do not import or call `cv2`. All rasterization, OpenCV calls, coordinate quantization, and alpha compositing are centralized strictly inside `OpenCVRenderer`.
19
+ 3. **Immutable Value Objects**: `Point` is an immutable `@dataclass(frozen=True)` with vector arithmetic. `Color` and `BoundingBox` enforce strict validation (no silent clamping of invalid channel or dimension values).
20
+ 4. **Clean Geometry Ownership**: `Scene` owns logical dimensions (`width`, `height`) and `background`. `Canvas` owns the resulting BGR NumPy array.
21
+ 5. **Exact Alpha Compositing**: Translucent strokes and fills are blended mathematically:
22
+ $$\text{effective\_alpha} = \text{color.a} \times \text{style.opacity} \times \text{drawable.opacity}$$
23
+ $$\text{dst} = \text{src} \cdot \alpha + \text{dst} \cdot (1 - \alpha)$$
24
+ 6. **No Silent Unsupported Behavior**: Unimplemented transforms (e.g. arbitrary rotation of rectangles or non-uniform scaling) raise `RenderError` rather than silently failing to render.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ Install via pip from PyPI:
31
+
32
+ ```bash
33
+ pip install pydrawcv
34
+ ```
35
+
36
+ Or install from source for development:
37
+
38
+ ```bash
39
+ git clone https://github.com/nuhyamin1/drawcv.git
40
+ cd drawcv
41
+ pip install -e ".[dev]"
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Coordinate System
47
+
48
+ DrawCV uses standard computer graphics screen coordinates:
49
+
50
+ - **Origin `(0, 0)`**: Top-left corner of the canvas.
51
+ - **+X axis**: Extends horizontally to the right.
52
+ - **+Y axis**: Extends vertically downward.
53
+ - **Subpixel Precision**: Model geometry is stored as floating-point numbers (`Point(120.35, 440.81)`). The renderer is responsible for coordinate quantization and anti-aliasing during rasterization.
54
+
55
+ ---
56
+
57
+ ## Quickstart Example
58
+
59
+ ```python
60
+ from drawcv import (
61
+ Scene,
62
+ OpenCVRenderer,
63
+ Point,
64
+ Color,
65
+ Circle,
66
+ Line,
67
+ Rectangle,
68
+ StrokeStyle,
69
+ FillStyle,
70
+ )
71
+
72
+ # 1. Create a retained Scene
73
+ scene = Scene(width=1200, height=800, background=Color.white())
74
+
75
+ # 2. Add shapes
76
+ circle = Circle(
77
+ center=Point(400, 300),
78
+ radius=100,
79
+ stroke=StrokeStyle(color=Color.black(), width=4.0),
80
+ fill=FillStyle(color=Color.from_hex("#73B9FF"), opacity=0.5)
81
+ )
82
+
83
+ line = Line(
84
+ start=Point(100, 100),
85
+ end=Point(800, 500),
86
+ stroke=StrokeStyle(color=Color.red(), width=5.0)
87
+ )
88
+
89
+ rectangle = Rectangle(
90
+ position=Point(700, 150),
91
+ width=220,
92
+ height=150,
93
+ stroke=StrokeStyle(color=Color.black(), width=3.0),
94
+ fill=FillStyle(color=Color.green(), opacity=0.4)
95
+ )
96
+
97
+ scene.add(circle)
98
+ scene.add(line)
99
+ scene.add(rectangle)
100
+
101
+ # 3. Retained-mode in-place manipulation
102
+ circle.center = Point(500, 350)
103
+ circle.radius = 130
104
+ line.stroke.color = Color.blue()
105
+ rectangle.width = 280
106
+
107
+ # 4. Render to Canvas
108
+ renderer = OpenCVRenderer()
109
+ canvas = renderer.render(scene)
110
+
111
+ # 5. Export to disk
112
+ canvas.save("output.png")
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Core Components (Phase 1)
118
+
119
+ ### Geometry & Color
120
+ - `Point(x, y)`: Immutable 2D vector supporting `+`, `-`, `*`, `/`, `.translate(dx, dy)`, `.distance_to(other)`.
121
+ - `Color(r, g, b, a=1.0)`: RGBA color model with `.from_hex()`, `.from_rgb()`, `.from_bgr()`, `.to_bgr()`, `.to_hex()`, `.black()`, `.white()`, `.red()`, etc.
122
+ - `BoundingBox(x, y, width, height)`: Spatial bounding box with `left`, `right`, `top`, `bottom`, `center`, `corners`, `.contains()`, `.intersects()`, `.union()`, `.expand()`.
123
+
124
+ ### Styling
125
+ - `StrokeStyle(color, width, opacity, line_type)`: Anti-aliased stroke styling (`LineType.AA`, `LINE_8`, `LINE_4`).
126
+ - `FillStyle(enabled, color, opacity)`: Interior shape fill styling.
127
+
128
+ ### Shapes
129
+ - `Line(start, end, stroke)`: Computes `length`, `angle`, `center`, and stroke-aware bounds.
130
+ - `Rectangle(position, width, height, stroke, fill)`: Computes `area`, `center`, `corners`, and stroke-aware bounds.
131
+ - `Circle(center, radius, stroke, fill)`: Computes `diameter`, `area`, `circumference`, and stroke-aware bounds.
132
+
133
+ ### Advanced Geometry & Manipulation (Phases 2 & 3)
134
+ - **Transforms & Manipulation**: `.move(dx, dy)`, `.rotate(deg, pivot)`, `.scale(sx, sy, pivot)`, `.clone()`, exact geometric hit-testing.
135
+ - **Advanced Shapes**: `Ellipse`, `Arc` (OPEN, CHORD, PIE), `Polygon`, `Polyline`, `Arrow` (TRIANGLE, OPEN, DIAMOND, CIRCLE heads), `BezierCurve` (quadratic and cubic), `Path` (retained vector path with `MoveTo`, `LineTo`, `QuadraticTo`, `CubicTo`, `Close`), and `RoundedRectangle`.
136
+ - **Topological Fill Rules**: `FillRule.EVEN_ODD` and `FillRule.NON_ZERO` evaluation.
137
+
138
+ ### Scene System (Phase 4)
139
+ - **Hierarchical Groups (`Group`)**: Container drawables supporting nested groups, cascaded transforms, non-destructive coordinate propagation, zero-shift world-transform reparenting, and collective world AABB computation.
140
+ - **Rendering Layers (`Layer`)**: Global rendering passes and stack organization (`z_order`), visibility, opacity, and cascading locked state.
141
+ - **Logical Selection (`Selection`)**: Multi-object selection controller with ancestor/descendant normalization (preventing double-transforms), rigid rotation around collective bounds center, scaling, batch property adjustments, and container-safe `.group()`.
142
+ - **Declarative Relative Positioning**: `align_left`, `align_right`, `align_top`, `align_bottom`, `align_center_x`, `align_center_y`, `align_centers`, `place_above`, `place_below`, `place_left_of`, `place_right_of` (with default `gap=20.0`), `distribute_horizontally`, `distribute_vertically`.
143
+ - **Authoritative Scene Registry & Query**: Fast $O(1)$ lookup via `scene.get(id)` across layers and groups, collision prevention, `find_by_name`, `find_by_tag`, `find_by_type`, `find_by_metadata`, and predicate searches.
144
+
145
+ ### Freehand Engine (Phase 5)
146
+ - **`StrokePoint`**: Immutable frozen point holding coordinates with physical capture metadata (`pressure`, `timestamp`, `velocity`).
147
+ - **`FreehandStroke`**: Retained-mode freehand stroke retaining authoritative raw sampled input points without mutation.
148
+ - **Modular Path Processing Pipeline**:
149
+ - **RDP Simplification (`rdp_simplify`)**: Noise and redundant point reduction while preserving salient corners and all metadata.
150
+ - **Chaikin Smoothing (`chaikin_smooth`)**: Iterative corner-cutting curve refinement with open endpoint preservation.
151
+ - **Catmull-Rom Spline (`catmull_rom_spline`)**: $C^1$-continuous cubic spline passing exactly through control points with centripetal parameterization ($\alpha=0.5$).
152
+ - **Linear Metadata Interpolation**: Clamped linear interpolation for pressure $[0.0, 1.0]$ and timestamps preventing overshoot and preserving physical monotonicity.
153
+ - **Variable-Width Contour Rasterization**: Segment-normal tapered quadrilateral ribbons with circular joint/cap discs for stylus calligraphy (pressure-sensitive) and fountain pen flick dynamics (velocity-sensitive).
154
+
155
+ ### Compositing & Effects (Phase 6)
156
+ - **Isolated Offscreen Surfaces**: Eliminates overlapping-geometry opacity accumulation by rasterizing translucent groups and layers to isolated premultiplied float32 BGRA buffers.
157
+ - **Single-Application Opacity Boundary**: Ancestor opacity is withheld during isolated subtree rendering and applied strictly once at the compositing boundary.
158
+ - **Premultiplied Alpha Pipeline**: Mathematically rigorous blending ($C_{\text{out}} = C_s + C_d(1 - \alpha_s)$, $\alpha_{\text{out}} = \alpha_s + \alpha_d(1 - \alpha_s)$) with 4-channel synchronous scaling across B, G, R, and A to prevent color fringing.
159
+ - **Effects**:
160
+ - `BlurEffect`: Content blur (`BlurType.GAUSSIAN`, `BlurType.BOX`) executed on premultiplied intermediates without boundary halos.
161
+ - `ShadowEffect`: Cast shadows with signed asymmetric offsets (`offset_x`, `offset_y`), blur radius, and shadow color, rendered behind base geometry.
162
+ - **Effect-Inflated Buffers**: Asymmetric buffer expansion accommodating signed shadow offsets and blur penumbras.
163
+ - **Clipping & Masking**:
164
+ - `ClipRect` & `ClipPath`: Vector stencils applied as the final post-effects clipping boundary.
165
+ - `Mask`: Grayscale luminance/alpha masks with `MaskMapping.FIT_BOUNDS` mapped against pre-effect visual bounds.
166
+ - **New Retained Primitives**:
167
+ - `ImageObject`: Raster images supporting BGR and BGRA, source cropping, target sizing, affine warps, and alpha compositing.
168
+ - `Text`: Typography with font families (`FontFamily.SANS_SERIF`, `SERIF`, `MONOSPACE`, `SCRIPT`), alignments (`LEFT`, `CENTER`, `RIGHT`), and background plates with optional `background_radius`.
169
+
170
+ - **Phase 7 Capabilities (Persistence & History Engine)**:
171
+ - **Canonical JSON Document Envelope**: Root header (`{"format": "drawcv", "version": "1.0", "scene": ...}`) with bit-for-bit deterministic serialization (`sort_keys=True`, `allow_nan=False`).
172
+ - **Strict Version Migration Pipeline**: `SchemaMigrator` enables sequential forward migrations for backward compatibility across schema versions.
173
+ - **Structured Raster Payload**: Lossless PNG base64 encoding with decoded shape and `uint8` dtype validation for `Mask` and `ImageObject`.
174
+ - **Lossless Float Alpha**: Colors serialize alpha as exact floating-point numbers (`0.5 == 0.5`), preventing 8-bit quantization drift.
175
+ - **In-Place Live Object Identity Preservation**: History undo/redo mutates existing live Python instances via `_apply_semantic_state()`, preserving references (`scene.get(id) is original_obj`).
176
+ - **Recursive Group Semantic Snapshots**: `Group._get_semantic_state()` captures descendant states and ordering; `Group._apply_semantic_state()` restores states in-place.
177
+ - **Reversible Operations**: `add`, `remove`, `move_to_front`, `move_to_back`, `move_forward`, `move_backward`, `group`, `ungroup`, `move_object`, `rotate_object`, `scale_object`, `restyle_object`, `with scene.edit(...)`, and `with scene.batch(...)`.
178
+ - **History Invariants**: Automatic redo branch invalidation, no-op edit filtering, exception rollback, and drift-free 50-cycle undo/redo.
179
+
180
+ - **Phase 8 Capabilities (Temporal Drawing & Animation Engine)**:
181
+ - **Timing & Pacing Model**: Dedicated `Timing` value object controlling `start_time`, `duration`, `delay`, `speed`, and `loop`, with clamped `get_progress()` for progressive reveal and raw `evaluate()` for property tracks.
182
+ - **Easing Suite**: 22 standard easing curves across Linear, Quadratic, Cubic, Sine, Exponential, Circular, Elastic, and Bounce families.
183
+ - **Arc-Length Progressive Path Slicing**: True Euclidean path-length parameterization for `Line`, `Polyline`, `Arrow`, `BezierCurve`, `Path`, `FreehandStroke`, and `Arc` (sweep angle reveal).
184
+ - **Stroke-First Reveal Semantics**: While `render_progress < 1.0`, shapes progressively expose outline stroke only; interior fills are suppressed until reaching full completion ($p = 1.0$).
185
+ - **Explicit Progressive Capability**: `supports_progressive_rendering` prevents slicing recursion and ensures non-path shapes (e.g. `Rectangle`, `Circle`) render safely.
186
+ - **Observational Non-Destructive Sampling**: `scene.render_at_time(t)` evaluates timestamps with suspended history and unconditionally restores authored state in `finally`, preserving live object identity and preventing history pollution.
187
+ - **Automatic Timing Binding**: `drawable.timing` automatically drives `drawable.render_progress` during temporal evaluation without requiring explicit Timeline tracks.
188
+ - **Synchronized Timeline**: Multi-track property animations with deterministic insertion-order conflict resolution and comprehensive `scene.temporal_duration` derivation.
189
+ - **Typed Value Serialization**: `AnimationTrack` persists typed value codecs (`"number"`, `"point"`, `"color"`, `"transform"`, `"bounds"`) and string-only easing contracts.
190
+ - **Document Schema 1.1 & Migration**: Automatic forward migration from Phase 7 (`"1.0"`) to `"1.1"`, injecting temporal defaults across layers and nested groups.
191
+ - **Multi-Frame & Video Export**: `VideoRenderer` encoding playable MP4 video via OpenCV `cv2.VideoWriter`, image sequences, and optional Pillow animated GIF.
192
+
193
+ ---
194
+
195
+ ## Running Tests & Demos
196
+
197
+ Run automated unit tests (240 tests, 100% green):
198
+
199
+ ```bash
200
+ pytest tests/ -v
201
+ ```
202
+
203
+ Run visual demonstrations:
204
+
205
+ ```bash
206
+ python examples/phase1_demo.py
207
+ python examples/phase2_demo.py
208
+ python examples/phase3_demo.py
209
+ python examples/phase4_demo.py
210
+ python examples/phase5_demo.py
211
+ python examples/phase6_demo.py
212
+ python examples/phase7_demo.py
213
+ python examples/progressive_drawing.py
214
+ python examples/phase8_demo.py
215
+ ```
216
+
217
+ Generated outputs will be saved to `examples/output/`.
218
+
219
+ ---
220
+
221
+ ## Development Roadmap
222
+
223
+ - [x] **Phase 1 — Foundation**: Retained scene graph, Point, Color, BoundingBox, Line, Rectangle, Circle, StrokeStyle, FillStyle, OpenCVRenderer, Canvas, true alpha compositing, unit tests, visual demos.
224
+ - [x] **Phase 2 — Manipulation**: Full affine transform rendering (rotation, non-uniform scaling), object cloning, hit testing.
225
+ - [x] **Phase 3 — Advanced Geometry**: Ellipse, Arc, Polygon, Polyline, Arrow, Bézier curves, General Path, RoundedRectangle.
226
+ - [x] **Phase 4 — Scene System**: Semantic Groups, Rendering Layers, Selection models, Relative positioning utilities, Centralized scene registry.
227
+ - [x] **Phase 5 — Freehand Engine**: FreehandStroke, StrokePoint, Chaikin smoothing, Ramer-Douglas-Peucker simplification, Catmull-Rom interpolation, pressure and velocity sensitive variable width.
228
+ - [x] **Phase 6 — Compositing & Effects**: Grayscale masks, clipping rectangles, clipping paths, ImageObject, Text, blur, drop shadows, isolated group/layer offscreen compositing.
229
+ - [x] **Phase 7 — Persistence & History**: Strict canonical JSON serialization, forward schema migration, command-based Undo/Redo engine, live identity preservation, recursive group state snapshots.
230
+ - [x] **Phase 8 — Temporal Drawing & Animation**: Timing metadata, arc-length progressive rendering, stroke-first reveal, 22 easing curves, non-destructive temporal sampling, Timeline multi-track synchronization, schema 1.1 migration, OpenCV MP4 video encoding.
231
+
232
+