Inzoomen op een afbeelding doet niets boven 100%: de vergroting wordt stil teruggeknepen #1813

Closed
opened 2026-08-27 13:42:31 +00:00 by brenno · 0 comments
Owner

Wat er misgaat

De zoomschuif op een afbeelding loopt van 100% tot 400%, maar elke stand boven
100% levert exact hetzelfde beeld op als 100%
. Inzoomen doet niets — niet op de
dia, en niet op het proefbeeld in de bijsnijddialoog zelf.

Het raakt elke afbeelding met een zoom: de paneelafbeelding van bulletsImage en
twoImages, de volledige-dia-afbeelding, de sectie- en titelachtergrond. Alle
vijf de aanroepen van de bijsnijddialoog zetten enableZoom: true.

Reproductie

  1. Maak een Bullets + afbeelding-dia en kies een afbeelding.
  2. Open bijsnijden en zet de zoom op 100%. Onthoud wat je ziet.
  3. Zet de zoom op 300% of 400%.
  4. Het proefbeeld verandert niet, en de dia erna evenmin.

Slepen om de uitsnede te verschuiven doet in die stand ook niets zichtbaars: de
verschuiving wordt berekend uit een overloop (overflowX = frameW * (scale - 1))
die er niet is.

Waar het zit

_zoomedImage (lib/widgets/slides/previews/media_previews_image.dart) maakt een
doos van slot × zoom/100 en zet daar de afbeelding met BoxFit.contain in:

return ClipRect(child: ColoredBox(child: LayoutBuilder(builder: (context, constraints) {
  final boxW = constraints.maxWidth * scale;
  final boxH = constraints.maxHeight * scale;
  return Align(alignment: alignment, child: SizedBox(width: boxW, height: boxH, ));
})));

Align legt zijn kind af met constraints.loosen() — dat houdt het slot als
maximum — en SizedBox dwingt zijn maat af binnen de meegegeven grenzen. Een
doos die groter is dan het slot wordt dus stil teruggeknepen tot het slot. De
ClipRect eromheen staat er voor een overloop die nooit ontstaat.

Meting (flutter test, exact dezelfde widgetketen, slot 512×720):

imageZoom doos die de code vraagt doos die Flutter aflegt
50 256 × 360 256 × 360
100 512 × 720 512 × 720
140 716,8 × 1008 512 × 720
200 1024 × 1440 512 × 720
400 2048 × 2880 512 × 720

Onder de 100% klopt het wél — dat is de enige reden dat de code ooit iets lijkt
te doen. Die standen zijn alleen niet met de schuif te bereiken (minZoom = 100);
ze komen uit een handgeschreven <!-- ocideck_image_zoom: 50 -->.

Waarom niemand het zag

De dialoog bouwt zijn eigen proefbeeld met hetzelfde patroon — Align
SizedBox(frameW * scale) binnen een Stack(fit: StackFit.expand), en die geeft
strakke grenzen mee. Editor en dia knijpen dus allebei even hard, en zijn het
netjes met elkáár eens terwijl ze het geen van beiden met de schuif eens zijn.
Geen enkele test rendert een ingezoomd paneel; imageZoom komt in test/ alleen
voor in de rondgangstoets van de markdown.

De reparatie

OverflowBox is wat de compositie mist: dat is de doos die zijn kind wél buiten
de grenzen van de ouder mag afleggen. Neem Transform.scale níét — het
commentaar in _zoomedImage legt uit waarom die er ooit uit is gehaald: een
transformatielaag wordt door RepaintBoundary.toImage onbetrouwbaar vastgelegd,
en dat is precies hoe elke raster-export gemaakt wordt.

Doe in dezelfde gang mee, want ze gaan alle drie over deze plek:

  • _zoomedImage zegt "Defensive cap (parse already clamps)" — het parseren klemt
    ocideck_image_zoom helemaal niet (markdown_service_parse.dart:236 is een
    kale int.tryParse(...) ?? 0). Die klem in de renderlaag is de enige die er is.
  • De dartdoc van showImageCropDialog zegt dat enableZoom onwaar is voor het
    bulletspaneel en de twee-afbeeldingenslots. Alle vijf de aanroepers geven
    true mee; de tak voor false (_cover op regel 149) is onbereikbaar.
  • In het commentaar op regel 468 staat een Chinees woord midden in een
    Nederlandse zin ("De slider volgt同步").

Regressietoets

Een widgettoets die de werkelijke afgelegde maat meet bij zoom 100 en 300 en eist
dat ze verschillen — de fout is onzichtbaar voor elke toets die alleen kijkt of
imageZoom de goede waarde heeft. Plus een beeldkeuring: dit is bij uitstek iets
wat je met eigen ogen moet zien.

Waarom nu

Gevonden bij het uitwerken van de geometrie voor #1801 (beeldverwijzingen), §4.3
van docs/design/IMAGE_CALLOUTS.md. Dat ontwerp legt vast hoe een punt in de
afbeelding naar een plek op de dia wordt gerekend, en die berekening moet op vier
oppervlakken hetzelfde uitpakken. Zolang Flutter de zoom wegknijpt en de
HTML-export hem wél zou toepassen, zetten die twee de markering op verschillende
plaatsen. Het ontwerp noemt deze reparatie daarom een voorwaarde vooraf, naast
#1803.

**Wat er misgaat** De zoomschuif op een afbeelding loopt van 100% tot 400%, maar **elke stand boven 100% levert exact hetzelfde beeld op als 100%**. Inzoomen doet niets — niet op de dia, en niet op het proefbeeld in de bijsnijddialoog zelf. Het raakt elke afbeelding met een zoom: de paneelafbeelding van `bulletsImage` en `twoImages`, de volledige-dia-afbeelding, de sectie- en titelachtergrond. Alle vijf de aanroepen van de bijsnijddialoog zetten `enableZoom: true`. **Reproductie** 1. Maak een `Bullets + afbeelding`-dia en kies een afbeelding. 2. Open bijsnijden en zet de zoom op 100%. Onthoud wat je ziet. 3. Zet de zoom op 300% of 400%. 4. Het proefbeeld verandert niet, en de dia erna evenmin. Slepen om de uitsnede te verschuiven doet in die stand ook niets zichtbaars: de verschuiving wordt berekend uit een overloop (`overflowX = frameW * (scale - 1)`) die er niet is. **Waar het zit** `_zoomedImage` (`lib/widgets/slides/previews/media_previews_image.dart`) maakt een doos van `slot × zoom/100` en zet daar de afbeelding met `BoxFit.contain` in: ```dart return ClipRect(child: ColoredBox(child: LayoutBuilder(builder: (context, constraints) { final boxW = constraints.maxWidth * scale; final boxH = constraints.maxHeight * scale; return Align(alignment: alignment, child: SizedBox(width: boxW, height: boxH, …)); }))); ``` `Align` legt zijn kind af met `constraints.loosen()` — dat houdt het slot als *maximum* — en `SizedBox` dwingt zijn maat af *binnen* de meegegeven grenzen. Een doos die groter is dan het slot wordt dus stil teruggeknepen tot het slot. De `ClipRect` eromheen staat er voor een overloop die nooit ontstaat. **Meting** (`flutter test`, exact dezelfde widgetketen, slot 512×720): | `imageZoom` | doos die de code vraagt | doos die Flutter aflegt | | ----------- | ----------------------- | ----------------------- | | 50 | 256 × 360 | 256 × 360 | | 100 | 512 × 720 | 512 × 720 | | 140 | 716,8 × 1008 | **512 × 720** | | 200 | 1024 × 1440 | **512 × 720** | | 400 | 2048 × 2880 | **512 × 720** | Onder de 100% klopt het wél — dat is de enige reden dat de code ooit iets lijkt te doen. Die standen zijn alleen niet met de schuif te bereiken (`minZoom = 100`); ze komen uit een handgeschreven `<!-- ocideck_image_zoom: 50 -->`. **Waarom niemand het zag** De dialoog bouwt zijn eigen proefbeeld met hetzelfde patroon — `Align` → `SizedBox(frameW * scale)` binnen een `Stack(fit: StackFit.expand)`, en die geeft strakke grenzen mee. Editor en dia knijpen dus allebei even hard, en zijn het netjes met elkáár eens terwijl ze het geen van beiden met de schuif eens zijn. Geen enkele test rendert een ingezoomd paneel; `imageZoom` komt in `test/` alleen voor in de rondgangstoets van de markdown. **De reparatie** `OverflowBox` is wat de compositie mist: dat is de doos die zijn kind wél buiten de grenzen van de ouder mag afleggen. Neem `Transform.scale` níét — het commentaar in `_zoomedImage` legt uit waarom die er ooit uit is gehaald: een transformatielaag wordt door `RepaintBoundary.toImage` onbetrouwbaar vastgelegd, en dat is precies hoe elke raster-export gemaakt wordt. Doe in dezelfde gang mee, want ze gaan alle drie over deze plek: - `_zoomedImage` zegt "Defensive cap (parse already clamps)" — het parseren klemt `ocideck_image_zoom` helemaal niet (`markdown_service_parse.dart:236` is een kale `int.tryParse(...) ?? 0`). Die klem in de renderlaag is de enige die er is. - De dartdoc van `showImageCropDialog` zegt dat `enableZoom` onwaar is voor het bulletspaneel en de twee-afbeeldingenslots. Alle vijf de aanroepers geven `true` mee; de tak voor `false` (`_cover` op regel 149) is onbereikbaar. - In het commentaar op regel 468 staat een Chinees woord midden in een Nederlandse zin ("De slider volgt同步"). **Regressietoets** Een widgettoets die de werkelijke afgelegde maat meet bij zoom 100 en 300 en eist dat ze verschillen — de fout is onzichtbaar voor elke toets die alleen kijkt of `imageZoom` de goede waarde heeft. Plus een beeldkeuring: dit is bij uitstek iets wat je met eigen ogen moet zien. **Waarom nu** Gevonden bij het uitwerken van de geometrie voor #1801 (beeldverwijzingen), §4.3 van `docs/design/IMAGE_CALLOUTS.md`. Dat ontwerp legt vast hoe een punt in de afbeelding naar een plek op de dia wordt gerekend, en die berekening moet op vier oppervlakken hetzelfde uitpakken. Zolang Flutter de zoom wegknijpt en de HTML-export hem wél zou toepassen, zetten die twee de markering op verschillende plaatsen. Het ontwerp noemt deze reparatie daarom een voorwaarde vooraf, naast #1803.
brenno 2026-08-27 21:21:25 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1813
No description provided.