geomotif.io.raster
¶
Turn a design into pixels, in pure standard library.
Everything else this library writes is vector, and for good reason: a design is a set of curves and the formats that keep them curves are the ones worth writing. This module exists for the pixels, which vector cannot be -- the frames of an animation, and the stills of PNG and JPEG.
The picture is drawn once as a full-color frame -- one RGBA value per
pixel, palette-free -- so that antialiasing, styling and every ink land in one
place and every encoder reads the same quality. Two output shapes come out of
it, in the :class:Raster type:
- A direct RGBA (or RGB) bitmap, the natural input for PNG and JPEG, which keep all 255 levels of color and edge.
- An indexed bitmap -- one palette entry per pixel -- which is what GIF
wants and what keeps a line drawing small. When antialiasing creates blends
the index frame cannot hold on its own, the RGBA frame is run through
:func:
quantize, which shrinks it to a shared palette of at most 256 colors (with optional error-diffusion dithering) and never drops an ink.
Index 0 of a palette is the background and the strokes take whatever indices their styles worked out to, so a two-pen design rasterizes in two colors without being told twice.
With antialiasing off (the default), rendering is just the whole-pixel, hard-edged Bresenham draw this module has always done; antialiasing supersamples and then blends by coverage, which is the only part that costs more than a plain single paint.
Classes:
| Name | Description |
|---|---|
Raster |
An in-memory picture, top-left origin. |
Functions:
| Name | Description |
|---|---|
colors_in |
Return the palette a set of designs needs, background first. |
colours_in |
Keep the British spelling working while it is phased out. |
rasterize |
Draw a design into an indexed bitmap. |
rasterize_rgba |
Draw a design into a full-color RGBA frame, supersampled and antialiased. |
quantize |
Shrink RGBA frames to one shared indexed palette of at most |
Raster
dataclass
¶
Raster(width: int, height: int, pixels: bytes, palette: tuple[str, ...] = (), mode: str = 'indexed')
An in-memory picture, top-left origin.
A :class:Raster holds either an indexed bitmap -- one palette index
per pixel, which is what GIF wants -- or a direct one -- RGB or RGBA
bytes per pixel, which is what PNG and JPEG want. Everything downstream
(antialiasing, styling, the encoders) feeds off one of the two, so the
picture is drawn once and encoded many ways.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Size in pixels. |
required |
height
|
int
|
Size in pixels. |
required |
pixels
|
bytes
|
|
required |
palette
|
tuple of str
|
The colors an indexed bitmap's indices name, as |
()
|
mode
|
str
|
|
'indexed'
|
colors_in
¶
colors_in(designs: Iterable[Design], *, ink: str, background: str) -> tuple[str, ...]
Return the palette a set of designs needs, background first.
Shared across every frame of an animation rather than worked out per frame: a GIF has one global color table, and an index that meant crimson in one frame and black in the next would make the whole thing flicker.
Source code in src/geomotif/io/raster.py
colours_in
¶
colours_in(designs: Iterable[Design], *, ink: str, background: str) -> tuple[str, ...]
Keep the British spelling working while it is phased out.
:func:colors_in is the name of this function now; this alias exists so
1.1.0 callers keep working, and it warns that it will go away in a future
major release.
Source code in src/geomotif/io/raster.py
rasterize
¶
rasterize(design: Design, *, width: int = 480, height: int = 480, padding: float = 8.0, bounds: Bounds | None = None, palette: Sequence[str] | None = None, ink: str = '#0b0b0b', background: str = '#ffffff', thickness: int = 1, dot_radius: int | None = None) -> Raster
Draw a design into an indexed bitmap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
design
|
Design
|
What to draw. |
required |
width
|
int
|
Canvas size in pixels. |
480
|
height
|
int
|
Canvas size in pixels. |
480
|
padding
|
float
|
Margin reserved on all four sides, in pixels. |
8.0
|
bounds
|
Bounds
|
The world rectangle to map onto the canvas. Defaults to the design's own, which is right for a single image and wrong for a frame of an animation -- there, pass the same bounds to every frame or the drawing will swim about as its extent changes. |
None
|
palette
|
sequence of str
|
colors the indices name, background first. Defaults to
|
None
|
ink
|
str
|
Default stroke color and the color behind everything. |
'#0b0b0b'
|
background
|
str
|
Default stroke color and the color behind everything. |
'#0b0b0b'
|
thickness
|
int
|
Stroke width in pixels. |
1
|
dot_radius
|
int
|
Radius for loose points. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Raster
|
Ready to hand to :func: |
Source code in src/geomotif/io/raster.py
rasterize_rgba
¶
rasterize_rgba(design: Design, *, width: int = 480, height: int = 480, padding: float = 8.0, bounds: Bounds | None = None, ink: str = '#0b0b0b', background: str = '#ffffff', thickness: int = 1, dot_radius: int | None = None, scale: int = _AA_SCALE, aa_level: int | None = None, transparent: bool = False) -> Raster
Draw a design into a full-color RGBA frame, supersampled and antialiased.
Every pixel is a real (r, g, b, a) value rather than a palette index, so
the edges can blend into the background -- which is the whole point of
antialiasing. The design is painted scale times as large and each
output pixel is the stroke color over the background weighted by the
fraction of its sub-pixels the ink covered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
design
|
Design
|
What to draw. |
required |
width
|
int
|
Canvas size in pixels. |
480
|
height
|
int
|
Canvas size in pixels. |
480
|
padding
|
float
|
Margin reserved on all four sides, in pixels. |
8.0
|
bounds
|
Bounds
|
The world rectangle to map onto the canvas. Pass the same bounds to every frame of an animation or the drawing will swim about. |
None
|
ink
|
str
|
Default stroke color and the color behind everything. When
|
'#0b0b0b'
|
background
|
str
|
Default stroke color and the color behind everything. When
|
'#0b0b0b'
|
thickness
|
int
|
Stroke width in pixels. |
1
|
dot_radius
|
int
|
Radius for loose points. Defaults to |
None
|
scale
|
int
|
Supersampling factor; |
_AA_SCALE
|
aa_level
|
int
|
If given, each sub-pixel coverage fraction is rounded to one of
|
None
|
transparent
|
bool
|
Leave the background empty rather than painting it. A pixel with no
ink becomes |
False
|
Returns:
| Type | Description |
|---|---|
Raster
|
An |
Source code in src/geomotif/io/raster.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | |
quantize
¶
quantize(frames: Sequence[Raster], *, seeds: Sequence[str] = (), max_colors: int = 256, dither: bool = True, transparent: bool = False) -> tuple[Raster, ...]
Shrink RGBA frames to one shared indexed palette of at most max_colors.
The palette is built once across every frame -- so an animation does not
flicker as the index a color means changes -- and is seeded with the
colors given in seeds first, background first, keeping them exact. Any
remaining color budget is filled from the blends the antialiasing made,
cut down with median-cut when there are more of them than budget. A seed
color is never dropped: asking for more seeds than max_colors raises
instead.
With transparent set, index 0 is reserved for empty space: a pixel
whose alpha lies below the half point maps there instead of to a color,
and the transparent slot does not count against the color budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frames
|
sequence of Raster
|
|
required |
seeds
|
sequence of str
|
colors that must survive exactly, background first, as |
()
|
max_colors
|
int
|
The largest palette the output may hold. Must be >= 1. |
256
|
dither
|
bool
|
Whether to error-diffuse (Floyd-Steinberg) the rounding of each pixel onto its neighbours, which keeps antialiased gradients smooth within a small palette. On by default, since indexed output is where the color budget bites. |
True
|
transparent
|
bool
|
Reserve index 0 for empty pixels and drop the background from the color budget. Off by default. |
False
|
Returns:
| Type | Description |
|---|---|
tuple of Raster
|
One indexed :class: |
Source code in src/geomotif/io/raster.py
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 | |