diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d69161..548d9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,8 @@ This project follows a pragmatic versioning approach: ## 2.0.0 - 2026-06-19 - Added - - **Image pixel interpolation** (`--i-pixels` flag, `pixels.py`, `frame.py`): new global flag `--i-pixels` adds bilinear interpolation between pixels to upscale final images; produces smoother, higher-resolution output from the same fractal computation; valid range 0–3 (0 = no interpolation, 3 = 4× resolution); effectively multiplies final image dimensions by `(i_pixels + 1)` so a 1024×1024 render with `--i-pixels 2` produces a 3072×3072 PNG; implemented via deterministic numpy-based bilinear interpolation using `PIL.Image.Resampling.BILINEAR`; metadata key `tranZoom:render:i_pixels` stores the interpolation level in PNG text chunks. + - **Image pixel interpolation** (`--i-pixels` flag, `pixels.py`, `frame.py`): new global flag `--i-pixels` adds interpolation between pixels to upscale final images; produces smoother, higher-resolution output from the same fractal computation; valid range 0–3 (0 = no interpolation, 3 = 4× resolution); effectively multiplies final image dimensions by `(i_pixels + 1)` so a 1024×1024 render with `--i-pixels 2` produces a 3072×3072 PNG; implemented via deterministic numpy-based interpolation using PIL's `Resampling` algorithms; default method is bilinear for best stability; metadata key `tranZoom:render:i_pixels` stores the interpolation level in PNG text chunks. + - **Pixel interpolation resampling method** (`--resample` flag, `pixels.py`): new global flag `--resample METHOD` allows selection of interpolation algorithm for pixel upscaling; available methods include bilinear (stable and fast, recommended), bicubic (smoother but slightly slower), lanczos (maximum quality, slowest), and others from PIL's `Resampling` enum; default is lanczos; choice persists in metadata, allowing re-renders with same settings to maintain consistency; particularly useful for tuning final output quality when combined with `--i-pixels`. - **Animation frame interpolation** (`--i-frames` flag, `pixels.py`, `zoom.py`): new `tranz zoom auto` flag `--i-frames` generates interpolated frames between each real fractal-computed frame; produces smoother, higher-FPS animations from fewer fractal computations; valid range 0–7 (0 = no interpolation, 7 = 8× FPS); effectively multiplies final FPS by `(i_frames + 1)` so `--fps 10 --i-frames 2` produces 30 effective FPS; supports both linear interpolation (default for first/last frame pairs) and **quadratic interpolation** (uses curr, next, next+1 frames for smoother acceleration); `InterpolatedFrameStream()` yields real + interpolated frames in animation order; metadata key `tranZoom:zoom:frame:i_frames` stores the interpolation level. - **New pixels.py module** (`pixels.py`): extracted all pixel rendering logic from `image.py` into dedicated 1440-line module; handles color palette application, histogram equalization, PNG/GIF/MP4 encoding, mark/overlay drawing, and pixel interpolation; new classes `RenderParameters` (single-image rendering), `RenderAnimationParameters` (animation rendering), `Pixels` (raw RGBA pixel array), `RenderedZoomFrame` (rendered animation frame with metadata); separates fractal computation concerns (escape-time iteration, arbitrary precision) from rendering concerns (color mapping, file I/O, interpolation). - **Quadratic frame interpolation** (`zoom.py`): new `QuadraticInterpolatedFrame()` function uses three-point quadratic interpolation for smoother frame blending than linear interpolation; takes `(curr, next, next+1)` frames to compute intermediate frame with acceleration/deceleration curve; blends RGB pixel values via quadratic Lagrange polynomial; automatically falls back to linear interpolation for last frame pair or when `use_quadratic=False`; controlled by `DEFAULT_USE_QUADRATIC` constant (default True). diff --git a/README.md b/README.md index 596ad98..c39a3bb 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ The tool can save all computations to a local DB. If allowed, it will use these - **Manual zoom session**: The `tranz zoom manual` command runs the same iterative frame navigation but prompts the user for a direction at each step (1–9, numpad layout: 5=center, 8=N, 6=E, etc.) instead of querying an LLM. Supports both Mandelbrot and Julia Set fractals. - **Sector scoring**: Each sector is scored on a 0–100 scale for `fractal_score` (visual complexity / zoom promise). When targeted search is active, an additional `target_match_score` (also 0–100) is blended in with a configurable weight. - **Image metadata**: All tranZoom PNG images embed rich metadata (`tranZoom:*` PNG text chunks) including frame coordinates, magnification, palette (`tranZoom:render:palette`), precision, per-pixel statistics (`n:min`, `n:max`, `nu:min`, `nu:max`, histogram summaries), and (for AI/manual sessions) the full LLM evaluation, model parameters, prompts, and zoom step count. -- **Pixel interpolation** (`--i-pixels`): Bicubic upscaling applied to the final rendered image to produce higher-resolution output without additional fractal computation. The `--i-pixels N` flag (valid range 0–3) inserts `N` interpolated pixels between each computed pixel in both dimensions, multiplying the final image size by `(N+1)`. For example, `--i-pixels 2` transforms a 1024×1024 computation into a 3072×3072 final image using deterministic bicubic interpolation. The fractal is computed once at the base resolution, then upscaled using weighted RGBA blending over 2×2 pixel neighborhoods. This produces smoother gradients and reduces visible pixelation at the cost of larger file sizes; no additional computation time beyond the interpolation pass itself (≪1 second for typical sizes). Metadata key `tranZoom:render:i_pixels` stores the interpolation level. Use this when you want poster-quality output from moderate computation cost. +- **Pixel interpolation** (`--i-pixels`): Lanczos/Bicubic/Bilinear upscaling applied to the final rendered image to produce higher-resolution output without additional fractal computation. The `--i-pixels N` flag (valid range 0–3) inserts `N` interpolated pixels between each computed pixel in both dimensions, multiplying the final image size by `(N+1)`. For example, `--i-pixels 2` transforms a 1024×1024 computation into a 3072×3072 final image using Lanczos interpolation. The fractal is computed once at the base resolution, then upscaled using weighted RGBA blending over 2×2 pixel neighborhoods. This produces smoother gradients and reduces visible pixelation at the cost of larger file sizes; no additional computation time beyond the interpolation pass itself (≪1 second for typical sizes). Metadata key `tranZoom:render:i_pixels` stores the interpolation level. Use this when you want poster-quality output from moderate computation cost. - **Frame interpolation** (`--i-frames`): Temporal interpolation applied to zoom animations to produce higher frame rates without computing additional fractal frames. The `--i-frames M` flag (valid range 0–7) inserts `M` interpolated frames between each real fractal-computed frame, multiplying the effective FPS by `(M+1)`. For example, `--fps 10 --i-frames 2` computes 10 real frames per second but outputs 30 total frames per second (10 real + 20 interpolated). Supports **linear interpolation** (weighted RGB blend between consecutive frames) and **quadratic interpolation** (three-point Lagrange curve using curr, next, next+1 frames for smoother acceleration). Quadratic mode is the default and produces more natural motion; it automatically falls back to linear for the final frame pair. This produces fluid animations from fewer expensive fractal computations; particularly effective for long zooms where computational cost dominates. The iteration depth (`max_iter`) is still computed per real frame using depth key frames, so quality remains consistent. Metadata key `tranZoom:zoom:frame:i_frames` stores the interpolation level. Use this when you want cinematic-smooth animations without the render time of computing every frame at full fractal precision. #### Frame Representation @@ -316,20 +316,23 @@ TransZoom 2.0 introduces two powerful interpolation modes that dramatically redu **Purpose:** Upscale a single rendered fractal image to higher resolution without computing additional fractal pixels. -**How it works:** After the fractal escape-time computation finishes, tranZoom applies deterministic bicubic interpolation to the output image. For each interpolated pixel, a weighted RGBA blend is computed from the surrounding 2×2 neighborhood using sub-pixel fractional coordinates. This produces smooth gradients and reduces visible pixelation, particularly effective for poster-quality prints or detailed zoom views. +**How it works:** After the fractal escape-time computation finishes, tranZoom applies deterministic interpolation to the output image using a selectable resampling method (default: Lanczos). The interpolation algorithm blends RGBA pixel values from the neighborhood around each sub-pixel coordinate. This produces smooth gradients and reduces visible pixelation, particularly effective for poster-quality prints or detailed zoom views. The resampling method can be customized via `--resample` for different quality/speed trade-offs. **Usage:** ```sh poetry run tranz --palette electric image -s 1024 --i-pixels 2 mandel +poetry run tranz --palette electric image -s 1024 --i-pixels 2 --resample bilinear mandel # more stable output ``` -This computes a 1024×1024 fractal (≈1.05M pixels), then upscales it to 3072×3072 (≈9.44M pixels) via bicubic interpolation. The fractal computation takes the same time as a normal 1024×1024 render; the interpolation pass adds only ≪1 second. +The first example computes a 1024×1024 fractal (≈1.05M pixels), then upscales it to 3072×3072 (≈9.44M pixels) via Lanczos interpolation. The second uses bilinear resampling, which is natively implemented and more guaranteed to be stable. **Parameters:** - **Flag:** `--i-pixels N` (valid range: 0–3) - **Effect:** For `N > 0`, the final image dimensions are `(width * (N+1), height * (N+1))` +- **Flag:** `--resample METHOD` (resampling algorithm: bilinear, bicubic, lanczos, etc.; default: lanczos) +- **Effect:** Selects the interpolation algorithm used for pixel upscaling; bilinear is fastest and most stable, bicubic/lanczos offer smoother output - **Examples:** - `--i-pixels 0` → no interpolation (default) - `--i-pixels 1` → 2× upscale (1024×1024 → 2048×2048) @@ -415,7 +418,7 @@ The computation cost is that of 10×512×512 fractal renders; pixel interpolatio ##### Implementation notes -- **Pixel interpolation:** Uses `PIL.Image.resize()` with `Resampling.BICUBIC`; deterministic and reproducible across platforms +- **Pixel interpolation:** Uses `PIL.Image.resize()` - **Frame interpolation:** Custom implementation in `zoom.py`; `InterpolatedFrameStream()` yields `bytes` (PNG-encoded frames) from an iterable of `(curr, next)` frame pairs; quadratic mode uses `QuadraticInterpolatedFrame()` with Lagrange polynomial blending - **Color consistency:** Both modes respect the `ZoomColorNorm` applied to the base computation, so interpolated pixels/frames inherit the same color mapping as surrounding real data - **Metadata roundtrip:** All interpolation settings are persisted in PNG text chunks and can be read back with `tranz image read`; re-rendering from a saved interpolated image will use the same settings @@ -567,7 +570,8 @@ tranz [global flags] image [-w W] [-h H] [-s S] [--iter N] [--mark COORD] None: # store this command's options in the shared config so all sub-commands can read it if ctx.invoked_subcommand is not None and ctx.obj is not None: + # check resample so it won't raise plain KeyError + res: str = resample.strip().upper() + if res not in pixels.Resampling.__members__: + raise base.UsageError( + f'Invalid interpolation resampling method {resample!r}; available methods: ' + + ', '.join(sorted(repr(c.name.lower()) for c in pixels.Resampling)) + ) # check color so it won't raise plain KeyError col: str = mark_color.strip().upper() if col not in pixels.Color.__members__: @@ -79,6 +87,7 @@ def ImageOptions( # documentation is in help/epilog # noqa: D103 img_height=img_height, img_size=img_size, i_pixels=i_pixels, + resample=pixels.Resampling[res], max_iter=max_iter, mark_coords=mark_coords, mark_color=pixels.Color[col], diff --git a/src/tranzoom/cli/zoomcommand.py b/src/tranzoom/cli/zoomcommand.py index c061915..c42d767 100644 --- a/src/tranzoom/cli/zoomcommand.py +++ b/src/tranzoom/cli/zoomcommand.py @@ -59,6 +59,7 @@ def ZoomOptions( # documentation is in help/epilog # noqa: D103 img_height: int = base.IMAGE_ZOOM_HEIGHT_OPTION, # type: ignore[assignment] img_size: int | None = base.IMAGE_SIZE_OPTION, # type: ignore[assignment] i_pixels: int = base.IMAGE_INTERPOLATION_PIXELS_OPTION, # type: ignore[assignment] + resample: str = base.IMAGE_INTERPOLATION_RESAMPLE_OPTION, # type: ignore[assignment] max_steps: int = base.MAX_STEPS_OPTION, # type: ignore[assignment] julia_re: str = base.JULIA_RE_OPTION, # type: ignore[assignment] julia_im: str = base.JULIA_IM_OPTION, # type: ignore[assignment] @@ -68,6 +69,13 @@ def ZoomOptions( # documentation is in help/epilog # noqa: D103 ) -> None: # store this command's options in the shared config so all sub-commands can read it if ctx.invoked_subcommand is not None and ctx.obj is not None: + # check resample so it won't raise plain KeyError + res: str = resample.strip().upper() + if res not in pixels.Resampling.__members__: + raise base.UsageError( + f'Invalid interpolation resampling method {resample!r}; available methods: ' + + ', '.join(sorted(repr(c.name.lower()) for c in pixels.Resampling)) + ) # check color so it won't raise plain KeyError col: str = mark_color.strip().upper() if col not in pixels.Color.__members__: @@ -82,6 +90,7 @@ def ZoomOptions( # documentation is in help/epilog # noqa: D103 img_height=img_height, img_size=img_size, i_pixels=i_pixels, + resample=pixels.Resampling[res], max_steps=max_steps, julia_re=julia_re, julia_im=julia_im, diff --git a/src/tranzoom/core/frdb.py b/src/tranzoom/core/frdb.py index f807abd..402ae64 100644 --- a/src/tranzoom/core/frdb.py +++ b/src/tranzoom/core/frdb.py @@ -1265,7 +1265,7 @@ def DoRender( # noqa: C901, PLR0915 f'{final_width} \u00d7 {final_height} ' f'(*{render.i_pixels + 1})' ) - img_data = img_data.Resize(final_width, final_height) + img_data = img_data.Resize(final_width, final_height, resample=render.resample) # draw crosshair mark if specified in render parameters if render.mark_color is not None: mark_pixel: tuple[int, int] diff --git a/src/tranzoom/core/image.py b/src/tranzoom/core/image.py index c636e5a..48bd438 100644 --- a/src/tranzoom/core/image.py +++ b/src/tranzoom/core/image.py @@ -52,6 +52,7 @@ META_RENDER_PALETTE_KEY: str = f'{_app}:render:palette' # str, ex "sunset", one of palette.Palette META_RENDER_SET_PALETTE_KEY: str = f'{_app}:render:set_palette' # str, interior Set palette name META_RENDER_I_PIXELS_KEY: str = f'{_app}:render:i_pixels' # int, number of interpolated pixels +META_RENDER_RESAMPLE_KEY: str = f'{_app}:render:resample' # str, one of pixels.Resampling.name.low META_RENDER_OVERLAY_KEY: str = f'{_app}:render:overlay' # image.OverlayType or "none" META_RENDER_MARK_RE_KEY: str = f'{_app}:render:mark_re' # gmpy2.mpq META_RENDER_MARK_IM_KEY: str = f'{_app}:render:mark_im' # gmpy2.mpq @@ -976,6 +977,7 @@ def MakeImageMeta(img: Image, render: pixels.RenderParameters, data_hash: str) - META_RENDER_SET_PALETTE_KEY: render.set_pal.value if render.set_pal else 'none', META_RENDER_OVERLAY_KEY: render.overlay.value if render.overlay else 'none', META_RENDER_I_PIXELS_KEY: str(render.i_pixels), + META_RENDER_RESAMPLE_KEY: str(render.resample.name.lower()), META_RENDER_MARK_RE_KEY: str(render.mark_re), META_RENDER_MARK_IM_KEY: str(render.mark_im), META_RENDER_MARK_COLOR_KEY: render.mark_color.name.lower() if render.mark_color else 'none', diff --git a/src/tranzoom/core/pixels.py b/src/tranzoom/core/pixels.py index b57ba30..6853f03 100644 --- a/src/tranzoom/core/pixels.py +++ b/src/tranzoom/core/pixels.py @@ -85,11 +85,11 @@ class Resampling(enum.IntEnum): # implemented in numpy locally BILINEAR = 100 # there is a PILImage.Resampling.BILINEAR, but we NEED THIS TO BE DIFFERENT # implemented in PIL - BICUBIC = PILImage.Resampling.BICUBIC - LANCZOS = PILImage.Resampling.LANCZOS + BICUBIC = PILImage.Resampling.BICUBIC # = 3 + LANCZOS = PILImage.Resampling.LANCZOS # = 1 -DEFAULT_RESAMPLING: Resampling = Resampling.BICUBIC +DEFAULT_RESAMPLING: Resampling = Resampling.LANCZOS # GIF has is_animated, but we have to check for it @@ -143,6 +143,7 @@ class RenderParameters(frame.SerializingFractalObject): set_pal (palette.Palette | None): Color palette for interior Set points; None means no Set palette (requires a non-Set computation); default is None. i_pixels (int): Number of pixels to interpolate between each pixel; default is 0 + resample (Resampling): Resampling filter to use for interpolation; default is DEFAULT_RESAMPLING mark_re (gmpy2.mpq): Real part of the optional crosshair mark coordinate; default is 0; unused when mark_color is None. mark_im (gmpy2.mpq): Imaginary part of the optional crosshair mark coordinate; @@ -160,13 +161,14 @@ class RenderParameters(frame.SerializingFractalObject): escaped_pal: palette.Palette = palette.DEFAULT_PALETTE set_pal: palette.Palette | None = None # if None, this must be a non-Set-computation i_pixels: int = 0 # for interpolation, number of pixels to interpolate between each pixel + resample: Resampling = DEFAULT_RESAMPLING mark_re: gmpy2.mpq = _MPQ_ZERO mark_im: gmpy2.mpq = _MPQ_ZERO mark_color: Color | None = None # if None, no mark will be drawn mark_width: int = DEFAULT_MARK_WIDTH overlay: OverlayType | None = None # overlay is independent of mark! - def __post_init__(self) -> None: + def __post_init__(self) -> None: # noqa: C901 """Check parameters for validity. Raises: @@ -186,6 +188,8 @@ def __post_init__(self) -> None: raise Error(f'Unknown set palette: {self.set_pal}') # check i_pixels is valid frame.ValidateIPixels(self.i_pixels) + if self.resample not in {Resampling.BILINEAR, Resampling.BICUBIC, Resampling.LANCZOS}: + raise Error(f'Unsupported resampling filter for rendering: {self.resample}') # check mark width is valid if not (MIN_MARK_WIDTH <= self.mark_width <= MAX_MARK_WIDTH): raise Error( @@ -238,9 +242,10 @@ def __str__(self) -> str: ) ) overlay: str = '' if self.overlay is None else f' + [OVERLAY: {self.overlay.name}]' + resample: str = f'/{self.resample.name.capitalize()}' if self.i_pixels else '' return ( '{' - f'[{self.tp.name.upper()}*{self.i_pixels + 1}: {self.escaped_pal.name}, ' + f'[{self.tp.name.upper()}*{self.i_pixels + 1}{resample}: {self.escaped_pal.name}, ' f'{self.set_pal.name if self.set_pal else "none"}]{mark}{overlay}' '}' ) @@ -262,6 +267,7 @@ def json(self) -> tbase.JSONDict: 'escaped_pal': self.escaped_pal.value, 'set_pal': self.set_pal.value if self.set_pal else None, 'i_pixels': self.i_pixels, + 'resample': self.resample.value, 'mark_re': str(self.mark_re), 'mark_im': str(self.mark_im), # BEWARE: we store the mark color as lowercase name, not the RGB value @@ -293,6 +299,7 @@ def FromJson(data: tbase.JSONDict, *, check_hash: str | None = None) -> RenderPa escaped_pal=palette.Palette(data.get('escaped_pal', palette.DEFAULT_PALETTE.value)), set_pal=palette.Palette(data['set_pal']) if data.get('set_pal') else None, i_pixels=int(str(data.get('i_pixels', '0'))), + resample=Resampling(int(str(data.get('resample', DEFAULT_RESAMPLING.value)))), mark_re=gmpy2.mpq(str(data.get('mark_re', '0'))), mark_im=gmpy2.mpq(str(data.get('mark_im', '0'))), mark_color=( # upper -> convert by name @@ -410,6 +417,7 @@ def FromRender( set_pal=render.set_pal, i_frames=i_frames, i_pixels=render.i_pixels, + resample=render.resample, mark_re=render.mark_re, mark_im=render.mark_im, mark_color=render.mark_color, diff --git a/tests/core/pixels_test.py b/tests/core/pixels_test.py index 3f462d7..e42d778 100644 --- a/tests/core/pixels_test.py +++ b/tests/core/pixels_test.py @@ -15,28 +15,30 @@ # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! _RENDER_STR_1: str = ( '{"escaped_pal":"sunset","i_pixels":1,"mark_color":null,"mark_im":"0","mark_re":"0",' - '"mark_width":1,"overlay":null,"set_pal":"rgrayscale","tp":"png"}' + '"mark_width":1,"overlay":null,"resample":3,"set_pal":"rgrayscale","tp":"png"}' ) _RENDER_STR_2: str = ( '{"escaped_pal":"electric","i_pixels":0,"mark_color":"red","mark_im":"9/2",' - '"mark_re":"-11/17","mark_width":2,"overlay":"grid","set_pal":null,"tp":"png"}' + '"mark_re":"-11/17","mark_width":2,"overlay":"grid","resample":1,"set_pal":null,"tp":"png"}' ) _RENDER_STR_3: str = ( '{"escaped_pal":"grayscale","i_pixels":3,"mark_color":"yellow","mark_im":"-7/11",' - '"mark_re":"71/4","mark_width":3,"overlay":null,"set_pal":"sunset","tp":"png"}' + '"mark_re":"71/4","mark_width":3,"overlay":null,"resample":100,"set_pal":"sunset","tp":"png"}' ) ANIM_STR_1: str = ( '{"anim":"gif","i_frames":0,"render":{"escaped_pal":"sunset","i_pixels":1,"mark_color":null,' - '"mark_im":"0","mark_re":"0","mark_width":1,"overlay":null,"set_pal":"rgrayscale","tp":"png"}}' + '"mark_im":"0","mark_re":"0","mark_width":1,"overlay":null,' + '"resample":3,"set_pal":"rgrayscale","tp":"png"}}' ) ANIM_STR_2: str = ( '{"anim":"mp4","i_frames":1,"render":{"escaped_pal":"electric","i_pixels":0,"mark_color":"red",' - '"mark_im":"9/2","mark_re":"-11/17","mark_width":2,"overlay":"grid","set_pal":null,"tp":"png"}}' + '"mark_im":"9/2","mark_re":"-11/17","mark_width":2,"overlay":"grid",' + '"resample":1,"set_pal":null,"tp":"png"}}' ) ANIM_STR_3: str = ( '{"anim":"gif","i_frames":2,"render":{"escaped_pal":"grayscale","i_pixels":3,' '"mark_color":"yellow","mark_im":"-7/11","mark_re":"71/4","mark_width":3,"overlay":null,' - '"set_pal":"sunset","tp":"png"}}' + '"resample":100,"set_pal":"sunset","tp":"png"}}' ) # DO NOT "JUST FIX" THESE! If they are wrong, it means something will break in the DB! @@ -48,6 +50,7 @@ 'e_pal', 's_pal', 'ip', + 'res', 'm_re', 'm_im', 'm_col', @@ -63,6 +66,7 @@ 'sunset', 'rgrayscale', 1, + pixels.Resampling.BICUBIC, '0', '0', None, @@ -70,9 +74,9 @@ None, # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! _RENDER_STR_1, # re-used below (zoom) to make sure it is all tied together - '2103c7db59d6e6a8d29b0ee4ab190095c16596eac74293253781d3ce3c48cd2b', # DO NOT "JUST FIX" + '56deab438b4453b358140917b24d12b9e3aeaec8c2d92381c1f2459e6196d207', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! - '{[PNG*2: SUNSET, GRAYSCALE_REVERSE]}', + '{[PNG*2/Bicubic: SUNSET, GRAYSCALE_REVERSE]}', id='RenderParameters-1', ), pytest.param( @@ -80,6 +84,7 @@ 'electric', None, 0, + pixels.Resampling.LANCZOS, '-11/17', '9/2', 'red', @@ -87,7 +92,7 @@ 'grid', # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! _RENDER_STR_2, # re-used below (zoom) to make sure it is all tied together - '439d95f21fd945fee8c7f06c2b3d0d1abf9e45ace4e227093a5d95cfc4860324', # DO NOT "JUST FIX" + 'd751524a3d651b73433d697157aabe811d507520be83ee7c588194ccca0febfa', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! '{[PNG*1: ELECTRIC, none] + [MARK: red/2 @ (-11/17, 9/2)] + [OVERLAY: GRID]}', id='RenderParameters-2', @@ -97,6 +102,7 @@ 'grayscale', 'sunset', 3, + pixels.Resampling.BILINEAR, '71/4', '-7/11', 'yellow', @@ -104,9 +110,9 @@ None, # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! _RENDER_STR_3, # re-used below (zoom) to make sure it is all tied together - '110d8105db06ef5c7bcfc4345a183b33a3d4fb4d7ce4170db2c881ee2967d00b', # DO NOT "JUST FIX" + '480fe1f8d9b32faa5d287ea1fdb7c5bf7cf9c6299da1a3655af9642cf9cf2dbb', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! - '{[PNG*4: GRAYSCALE, SUNSET] + [MARK: yellow/3 @ (71/4, -7/11)]}', + '{[PNG*4/Bilinear: GRAYSCALE, SUNSET] + [MARK: yellow/3 @ (71/4, -7/11)]}', id='RenderParameters-3', ), ], @@ -116,6 +122,7 @@ def test_render_png_hash_stability_and_serialization_consistency( e_pal: str, s_pal: str | None, ip: int, + res: pixels.Resampling, m_re: str, m_im: str, m_col: str | None, @@ -131,6 +138,7 @@ def test_render_png_hash_stability_and_serialization_consistency( escaped_pal=palette.Palette(e_pal), set_pal=palette.Palette(s_pal) if s_pal else None, i_pixels=ip, + resample=res, mark_re=gmpy2.mpq(m_re), mark_im=gmpy2.mpq(m_im), mark_color=pixels.Color[m_col.upper()] if m_col is not None else None, @@ -163,9 +171,9 @@ def test_render_png_hash_stability_and_serialization_consistency( 0, # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! ANIM_STR_1, # re-used below (zoom) to make sure it is all tied together - '58eedf88f7892580d0319cd3b4715d71c773e00a56dc06f34a45bf0c855333df', # DO NOT "JUST FIX" + '971d28ec5018bb2f577105f04a9be59dbc82ba1112bd37fdc4a718d04d33b5f6', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! - '', + '', id='RenderAnimationParameters-1', ), pytest.param( @@ -174,7 +182,7 @@ def test_render_png_hash_stability_and_serialization_consistency( 1, # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! ANIM_STR_2, # re-used below (zoom) to make sure it is all tied together - '821be148451935d2adfb016d14033077dd4283ba0d12de9812e89dfdf71653a1', # DO NOT "JUST FIX" + '8cdae0435ea5195c8ea785ccd6f359df3807dde5cf408a629f1b4c2c26a8be00', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! '', id='RenderAnimationParameters-2', @@ -185,9 +193,9 @@ def test_render_png_hash_stability_and_serialization_consistency( 2, # ATTENTION: if these change/break, ever, BIG PROBLEM!! b/c hashes will break in DB!! ANIM_STR_3, # re-used below (zoom) to make sure it is all tied together - '9a999088fd67c6308133ae86766c474dcc7b4da27cc0482705ee3e9713b0f284', # DO NOT "JUST FIX" + '77f58d02da3c21bd92f886949121a29abef558f356bb08d91f23a45cce88e2a3', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! - '', + '', id='RenderAnimationParameters-3', ), ], diff --git a/tests/core/zoom_test.py b/tests/core/zoom_test.py index 1fca15b..204dd75 100644 --- a/tests/core/zoom_test.py +++ b/tests/core/zoom_test.py @@ -41,13 +41,13 @@ '"height":512,"set_points":null,"width":512},"loop":0,"mag":"40/3","n_frames":17,' '"render":{"anim":"gif","i_frames":0,"render":{"escaped_pal":"sunset","i_pixels":1,' '"mark_color":null,"mark_im":"0","mark_re":"0","mark_width":1,"overlay":null,' - '"set_pal":"rgrayscale","tp":"png"}}}' + '"resample":3,"set_pal":"rgrayscale","tp":"png"}}}' ), - 'b83f2dfefc65ae813aae7a6bf32bf76583142eefafcf35e894277c9a3d68f287', # DO NOT "JUST FIX" + 'd2bc1be0f00094f71250a637fd1d57113ba674f21eee100264fd69afe48a1fe9', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! ( '<{[MANDELBROT: (0, 0) ± 2] : [512 × 512, 9999]} -> ' # noqa: RUF001 - ' / ' + ' / ' '(mag:40/3, n:17|17, d:2, fps:(17/2)*1, l:0)>' ), id='ZoomParameters-1', @@ -66,9 +66,10 @@ '"height":1024,"set_points":"imaginary","width":1024},"loop":0,"mag":"3/7",' '"n_frames":1000,"render":{"anim":"mp4","i_frames":1,"render":' '{"escaped_pal":"electric","i_pixels":0,"mark_color":"red","mark_im":"9/2",' - '"mark_re":"-11/17","mark_width":2,"overlay":"grid","set_pal":null,"tp":"png"}}}' + '"mark_re":"-11/17","mark_width":2,"overlay":"grid","resample":1,' + '"set_pal":null,"tp":"png"}}}' ), - 'fff17a04368fe6a2430d0ad79d61cfee57b2a6a74f78bfef400f7a09b81b6ccc', # DO NOT "JUST FIX" + '6441d20dc06c806d80a36882ed35354c8ae64f2a883f3372b7116c7a3c933f7f', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! ( '<{[JULIA: (0, 0) ± 2 @ (1, 1)] : [1024 × 1024, 6666] : imaginary} -> ' # noqa: RUF001 @@ -91,13 +92,13 @@ '"height":2048,"set_points":"max","width":2048},"loop":2,"mag":"750","n_frames":100,' '"render":{"anim":"gif","i_frames":2,"render":{"escaped_pal":"grayscale","i_pixels":3,' '"mark_color":"yellow","mark_im":"-7/11","mark_re":"71/4","mark_width":3,"overlay":null,' - '"set_pal":"sunset","tp":"png"}}}' + '"resample":100,"set_pal":"sunset","tp":"png"}}}' ), - '70129081c883c6776dd560592f47e4ea8c6106f7447485d0276ef52b9eb400ed', # DO NOT "JUST FIX" + '0b1aa165b88109ce7a85e3564f4cf2332367e9b2636a9f81df3aa95feab87bcc', # DO NOT "JUST FIX" # DO NOT "JUST FIX" THIS HASH! If the hash is wrong, it means something will break in the DB! ( '<{[JULIA: (-159/713, 64/133) ± (364/713, 366/133) @ (3/2, -11/19)] : ' - '[2048 × 2048, 8888] : max} -> / (mag:750, n:100|298, d:20, fps:(5)*3, l:2)>' ), id='ZoomParameters-3', diff --git a/tests/data/images/demo-julia-suzana-wave.png b/tests/data/images/demo-julia-suzana-wave.png index b1109ca..a3bf6fd 100644 Binary files a/tests/data/images/demo-julia-suzana-wave.png and b/tests/data/images/demo-julia-suzana-wave.png differ diff --git a/tests/data/images/demo-mandel-seahorse-tail-anim.gif b/tests/data/images/demo-mandel-seahorse-tail-anim.gif index 3ce0633..3a03e7c 100644 Binary files a/tests/data/images/demo-mandel-seahorse-tail-anim.gif and b/tests/data/images/demo-mandel-seahorse-tail-anim.gif differ diff --git a/tests/data/images/demo-mandel-seahorse-tail.png b/tests/data/images/demo-mandel-seahorse-tail.png index c5878ca..da6b42a 100644 Binary files a/tests/data/images/demo-mandel-seahorse-tail.png and b/tests/data/images/demo-mandel-seahorse-tail.png differ diff --git a/tests/data/images/test-julia-z-auto-blob.gif b/tests/data/images/test-julia-z-auto-blob.gif index d923f0c..26c8118 100644 Binary files a/tests/data/images/test-julia-z-auto-blob.gif and b/tests/data/images/test-julia-z-auto-blob.gif differ diff --git a/tests/data/images/test-julia-z-auto-dragon.gif b/tests/data/images/test-julia-z-auto-dragon.gif index 0bd1d4d..fd59708 100644 Binary files a/tests/data/images/test-julia-z-auto-dragon.gif and b/tests/data/images/test-julia-z-auto-dragon.gif differ diff --git a/tests/data/images/test-julia-z-auto-suzana.gif b/tests/data/images/test-julia-z-auto-suzana.gif index 5053bfc..ab28230 100644 Binary files a/tests/data/images/test-julia-z-auto-suzana.gif and b/tests/data/images/test-julia-z-auto-suzana.gif differ diff --git a/tests/data/images/test-mandel-z-auto-seahorse.gif b/tests/data/images/test-mandel-z-auto-seahorse.gif index fd4fc39..859fa64 100644 Binary files a/tests/data/images/test-mandel-z-auto-seahorse.gif and b/tests/data/images/test-mandel-z-auto-seahorse.gif differ diff --git a/tests/data/images/test-mandel-z-auto-seeds300.gif b/tests/data/images/test-mandel-z-auto-seeds300.gif index 81b023d..a1dc4ef 100644 Binary files a/tests/data/images/test-mandel-z-auto-seeds300.gif and b/tests/data/images/test-mandel-z-auto-seeds300.gif differ diff --git a/tests_integration/test_cython_equivalence.py b/tests_integration/test_cython_equivalence.py index fc7f815..55df97c 100644 --- a/tests_integration/test_cython_equivalence.py +++ b/tests_integration/test_cython_equivalence.py @@ -98,6 +98,8 @@ def test_python_cython_equivalence_seahorse(cli: pathlib.Path, opt: str) -> None '53', '--i-pixels', '2', + '--resample', + 'bilinear', 'auto', ' -0.7436499', '0.13188204', @@ -120,6 +122,9 @@ def test_python_cython_equivalence_seahorse(cli: pathlib.Path, opt: str) -> None output_image: pathlib.Path = ( pathlib.Path(tmp_dir) / f'mandel-{base.T_GIF_SEAHORSE_HASH[:20]}.gif' ) + for f in sorted(pathlib.Path(tmp_dir).glob('**/*')): + if f.is_file(): + print(f'Found file: {str(f)!r}') # noqa: T201 assert output_image.exists(), f'Expected output image not found: {output_image}' # check the image data info: pixels.ObjInfo @@ -182,8 +187,9 @@ def test_python_cython_equivalence_seahorse(cli: pathlib.Path, opt: str) -> None 'tranZoom:image:set:nu:min': '0.49376627802848816', 'tranZoom:image:stats:imag_hi': '0.05734383622578064069611499703175854039165102', 'tranZoom:image:stats:imag_lo': '0.05734383622578064069611499703175854039165102', - 'tranZoom:render:hash': 'e5ed9b7875faed4f26e63f27117eb8ba8f88d58f0f6189d3b0dc55e5c895f584', + 'tranZoom:render:hash': '47ed64f6232f9203d367251c599190ad7d668d144c11bb4f9420ec777a222ed9', 'tranZoom:render:i_pixels': '2', + 'tranZoom:render:resample': 'bilinear', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_re': '0', @@ -209,7 +215,7 @@ def test_python_cython_equivalence_seahorse(cli: pathlib.Path, opt: str) -> None 'tranZoom:zoom:frame:magnitude': '131/43', 'tranZoom:zoom:frame:seconds': '317/10', 'tranZoom:zoom:frame:steps': '30', - 'tranZoom:zoom:hash': 'f9eb196b73a9aa2706f48eb5b5d1c08283ddecd06483c34200aefb745d76af6e', + 'tranZoom:zoom:hash': '3d079241ec8521eb9795ca0969b4bba269d61cdea876c59179fce1b33a528e19', 'tranZoom:zoom:marker:index': '[0, 10, 20, 30]', 'tranZoom:zoom:type': 'gif', } @@ -318,6 +324,9 @@ def test_python_cython_equivalence_seeds300(cli: pathlib.Path, opt: str) -> None output_image: pathlib.Path = ( pathlib.Path(tmp_dir) / f'mandel-{base.T_GIF_SEEDS_300_HASH[:20]}.gif' ) + for f in sorted(pathlib.Path(tmp_dir).glob('**/*')): + if f.is_file(): + print(f'Found file: {str(f)!r}') # noqa: T201 assert output_image.exists(), f'Expected output image not found: {output_image}' # check the image data info: pixels.ObjInfo @@ -509,8 +518,9 @@ def test_python_cython_equivalence_seeds300(cli: pathlib.Path, opt: str) -> None 'tranZoom:image:set:n:min': '0', 'tranZoom:image:set:nu:max': '0.0', 'tranZoom:image:set:nu:min': '0.0', - 'tranZoom:render:hash': '25389b1c9ca4c515a1a40711aff3bfeb638a43f0ff3152cef955c4875701324f', + 'tranZoom:render:hash': 'efb080883e173221d94b40a4d77115192c72cd69a05853973a66c772a7300ad1', 'tranZoom:render:i_pixels': '3', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_re': '0', @@ -544,7 +554,7 @@ def test_python_cython_equivalence_seeds300(cli: pathlib.Path, opt: str) -> None 'tranZoom:zoom:frame:magnitude': '43/41', 'tranZoom:zoom:frame:seconds': '101/10', 'tranZoom:zoom:frame:steps': '9', - 'tranZoom:zoom:hash': 'b14e515756b728bf45b4f6c721da0324c5cd2b58b5632c2c5afd878628043910', + 'tranZoom:zoom:hash': '2301347a0342bff7cb7624b759613f5431627618b231c200ed842336483fe85f', 'tranZoom:zoom:marker:index': '[0, 9]', 'tranZoom:zoom:type': 'gif', } @@ -620,6 +630,9 @@ def test_python_cython_equivalence_suzana(cli: pathlib.Path, opt: str) -> None: output_image: pathlib.Path = ( pathlib.Path(tmp_dir) / f'julia-{base.T_GIF_JULIA_SUZANA_HASH[:20]}.gif' ) + for f in sorted(pathlib.Path(tmp_dir).glob('**/*')): + if f.is_file(): + print(f'Found file: {str(f)!r}') # noqa: T201 assert output_image.exists(), f'Expected output image not found: {output_image}' # check the image data info: pixels.ObjInfo @@ -694,8 +707,9 @@ def test_python_cython_equivalence_suzana(cli: pathlib.Path, opt: str) -> None: 'tranZoom:image:set:nu:min': '0.0', 'tranZoom:image:stats:ang_hi': '0.73519948957591444695345637018854658157873099', 'tranZoom:image:stats:ang_lo': '0.50658202515724162651223774275343995527044469', - 'tranZoom:render:hash': '5b5881924bb9d30d8aaec378d3053b0484bdf141e4ccfbe6143df82d67321c0b', + 'tranZoom:render:hash': 'f60ec96d8ba455a5936edc5d5401aabbbd932aa3900160c8d7d0572af5e76b6c', 'tranZoom:render:i_pixels': '1', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_re': '0', @@ -720,7 +734,7 @@ def test_python_cython_equivalence_suzana(cli: pathlib.Path, opt: str) -> None: 'tranZoom:zoom:frame:magnitude': '241/139', 'tranZoom:zoom:frame:seconds': '107/10', 'tranZoom:zoom:frame:steps': '20', - 'tranZoom:zoom:hash': '621d6d2b88c7842bdc701b7e8d18533080f784d8c000f89742cdd0ee05f0f961', + 'tranZoom:zoom:hash': '05be3db6449b73af69bd22b38ad957afa5783219bced59f086e09f0697c85bd3', 'tranZoom:zoom:marker:index': '[0, 20]', 'tranZoom:zoom:type': 'gif', } @@ -796,6 +810,9 @@ def test_python_cython_equivalence_dragon(cli: pathlib.Path, opt: str) -> None: output_image: pathlib.Path = ( pathlib.Path(tmp_dir) / f'julia-{base.T_GIF_JULIA_DRAGON_HASH[:20]}.gif' ) + for f in sorted(pathlib.Path(tmp_dir).glob('**/*')): + if f.is_file(): + print(f'Found file: {str(f)!r}') # noqa: T201 assert output_image.exists(), f'Expected output image not found: {output_image}' # check the image data info: pixels.ObjInfo @@ -871,8 +888,9 @@ def test_python_cython_equivalence_dragon(cli: pathlib.Path, opt: str) -> None: 'tranZoom:image:set:nu:min': '0.0', 'tranZoom:image:stats:min_hi': '0.082874941584400487748646457435133671445430575', 'tranZoom:image:stats:min_lo': '3.6863012726222845779975027843355758506749315e-05', - 'tranZoom:render:hash': 'd692016a50498841ea7b5c2982f4cef2c3be3b2deec5b9f82cfc3a112f3c94f5', + 'tranZoom:render:hash': 'ef4ebc337eafe4e9120de9648ac4e4fa787611e4f4425d47fb4d592020488f9c', 'tranZoom:render:i_pixels': '1', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_re': '0', @@ -894,7 +912,7 @@ def test_python_cython_equivalence_dragon(cli: pathlib.Path, opt: str) -> None: 'tranZoom:zoom:frame:magnitude': '37/97', 'tranZoom:zoom:frame:seconds': '41/10', 'tranZoom:zoom:frame:steps': '7', - 'tranZoom:zoom:hash': '8b1eac4479ba0f194e4ab174b614cd794e2c686054c015953ed63ba3b45e67a0', + 'tranZoom:zoom:hash': '3b1b2d08b8cd20d639100fa170cc644b79c8841ee0db9a3736f535eb7fa1c019', 'tranZoom:zoom:marker:index': '[0, 7]', 'tranZoom:zoom:type': 'gif', } @@ -970,6 +988,9 @@ def test_python_cython_equivalence_blob(cli: pathlib.Path, opt: str) -> None: output_image: pathlib.Path = ( pathlib.Path(tmp_dir) / f'julia-{base.T_GIF_JULIA_BLOB_HASH[:20]}.gif' ) + for f in sorted(pathlib.Path(tmp_dir).glob('**/*')): + if f.is_file(): + print(f'Found file: {str(f)!r}') # noqa: T201 assert output_image.exists(), f'Expected output image not found: {output_image}' # check the image data info: pixels.ObjInfo @@ -1046,8 +1067,9 @@ def test_python_cython_equivalence_blob(cli: pathlib.Path, opt: str) -> None: 'tranZoom:image:set:nu:min': '0.0', 'tranZoom:image:stats:max_hi': '1.9748028908146782087476064434619381192680041', 'tranZoom:image:stats:max_lo': '0.26279710060872639895079446071425291716398127', - 'tranZoom:render:hash': 'bdfaebb0bd66c67a5a8b3fb19316040421d9fb2b86db4037abc67ddea6eaf20b', + 'tranZoom:render:hash': '030e514d8c42aad58277657d981ce5950d145b61b14d5ea10e8513daa93852ec', 'tranZoom:render:i_pixels': '1', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_re': '0', @@ -1069,7 +1091,7 @@ def test_python_cython_equivalence_blob(cli: pathlib.Path, opt: str) -> None: 'tranZoom:zoom:frame:magnitude': '37/97', 'tranZoom:zoom:frame:seconds': '43/10', 'tranZoom:zoom:frame:steps': '7', - 'tranZoom:zoom:hash': '558e31a6c4f3ef7200e9b22b9eadb41a33a22fa15b4cf4fa8040184a619b58e5', + 'tranZoom:zoom:hash': '7bf8909303f774dd210c388ff13021a6b4b9635f398b307995bc43e9fa17f500', 'tranZoom:zoom:marker:index': '[0, 7]', 'tranZoom:zoom:type': 'gif', } diff --git a/tests_integration/test_installed_cli.py b/tests_integration/test_installed_cli.py index ae63daf..010cf83 100644 --- a/tests_integration/test_installed_cli.py +++ b/tests_integration/test_installed_cli.py @@ -161,11 +161,12 @@ def test_mandelbrot_seahorse_tail(cli: pathlib.Path) -> None: 'tranZoom:render:palette': 'sahara', 'tranZoom:render:set_palette': 'rgrayscale', 'tranZoom:render:i_pixels': '0', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_re': '0', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_width': '1', - 'tranZoom:render:hash': 'a1747d5f60f817fab6bcd70db4f772813f361d9e0c75f9e700e36c0a757906d9', + 'tranZoom:render:hash': '01226cc2b028d8f397f3c19e49d07476141c4ad740520136ddd5e88329fc82a8', } @@ -277,11 +278,12 @@ def test_animated_seahorse_tail(cli: pathlib.Path) -> None: 'tranZoom:render:palette': 'sahara', 'tranZoom:render:set_palette': 'none', 'tranZoom:render:i_pixels': '0', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'red', 'tranZoom:render:mark_re': '-5578776469/7500000000', 'tranZoom:render:mark_im': '8244620127/62500000000', 'tranZoom:render:mark_width': '1', - 'tranZoom:render:hash': '7d39836df7b681893fb77a59a3837d86e3897dbd5a70a233100d35d53aaff49e', + 'tranZoom:render:hash': 'f93b0c787429c192ac8914775512c02f47cfff5a1f5491657acded78e92f5d79', 'tranZoom:zoom:type': 'gif', 'tranZoom:zoom:frame:initial:width_re': '73801/100000000', 'tranZoom:zoom:frame:initial:height_im': '73801/100000000', @@ -300,7 +302,7 @@ def test_animated_seahorse_tail(cli: pathlib.Path) -> None: 'tranZoom:zoom:depth:frames': ( '[(0, 1000, 1001), (6, 1000, 1019), (13, 1000, 1034), (19, 1159, 1105)]' ), - 'tranZoom:zoom:hash': '8ae54d2302317afa4cb4c6cb971272ee8dea2e3f53040104397517bfc5444b93', + 'tranZoom:zoom:hash': '550c3d1295e02795ef212b5e7429a15c62b41840b1af809a719e92f47e2d9d27', } @@ -432,9 +434,10 @@ def test_julia_suzana_wave(cli: pathlib.Path) -> None: 'tranZoom:render:palette': 'electric', 'tranZoom:render:set_palette': 'sunset', 'tranZoom:render:i_pixels': '1', + 'tranZoom:render:resample': 'lanczos', 'tranZoom:render:mark_color': 'none', 'tranZoom:render:mark_re': '0', 'tranZoom:render:mark_im': '0', 'tranZoom:render:mark_width': '1', - 'tranZoom:render:hash': 'f56145e999d631c6f6ef4dc52b0b502766047aca4b3f3f99156bac5b63a91df6', + 'tranZoom:render:hash': '478b636f99b98fabde204dec8bc701b92ac9977028c749946bb23d9a2deb0f11', } diff --git a/tranz.md b/tranz.md index 62a785f..9efd134 100644 --- a/tranz.md +++ b/tranz.md @@ -553,6 +553,13 @@ Usage: tranz image [OPTIONS] COMMAND [ARGS]... │ interpolated pixel between every pair │ │ of pixels, etc │ │ │ +│ --resample TEXT Interpolation resampling method; │ +│ default is "lanczos"; "bilinear" has │ +│ the most stable results; "lanczos" is │ +│ the most accurate but slowest; │ +│ available values: 'bicubic', │ +│ 'bilinear', 'lanczos' │ +│ │ │ --iter -i INTEGER RANGE [1001<=x<=2147483647] Maximum iterations (depth) to compute │ │ before determining escape; 1001 ≤ │ │ iter ≤ 2147483647; default is None │ @@ -838,6 +845,12 @@ Usage: tranz zoom [OPTIONS] COMMAND [ARGS]... │ interpolated pixel between every pair of │ │ pixels, etc │ │ │ +│ --resample TEXT Interpolation resampling method; default is │ +│ "lanczos"; "bilinear" has the most stable │ +│ results; "lanczos" is the most accurate but │ +│ slowest; available values: 'bicubic', │ +│ 'bilinear', 'lanczos' │ +│ │ │ --max-steps -n INTEGER RANGE Maximum number of zoom steps to run; 0 means │ │ run until manually stopped (Ctrl+C); default │ │ is 0 (unlimited, run forever) │