Skip to content

Commit 8ade24d

Browse files
committed
docs(photo): the 1024px bound is on the shortest edge, not the longest (#1001)
Three comments claimed `minWidth`/`minHeight` cap the longest edge. They bound the shortest one. flutter_image_compress takes `max(1, min(width/minWidth, height/minHeight))` as its scale factor (BitmapCompressExt.kt:62-66; UIImage+scale.m:12-21 picks the same branch), so the smaller ratio wins and the edge landing on 1024 is the short one. A 4080x3072 camera frame comes out 1360x1024, not 1024x768 -- about 1.8x the pixels the comments promised, and more again at 16:9. Comments only; the calls are unchanged and the behaviour was always this. Corrected in meal_photo_encoder.dart and user_image_storage.dart, where the claim appeared twice each (dartdoc summary and the call site), and in recipe_builder_screen.dart, which repeated it when explaining why it picks at full resolution. Found while checking docs/ai-architecture.md, which stated the same thing and is corrected in #996. (cherry picked from commit 202a21b)
1 parent b031f13 commit 8ade24d

3 files changed

Lines changed: 18 additions & 12 deletions

File tree

lib/core/utils/user_image_storage.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ enum UserImageKind {
3131
/// across app reinstalls and iOS sandbox refreshes, where the documents
3232
/// directory's parent prefix can change between launches.
3333
///
34-
/// Photos are stored as WebP at quality 80, capped at 1024px on the
35-
/// longest edge — small enough that an export zip of a few dozen
34+
/// Photos are stored as WebP at quality 80, bounded at 1024px on the
35+
/// shortest edge — small enough that an export zip of a few dozen
3636
/// recipes and meals stays in the low-megabyte range, while still
3737
/// being plenty crisp for a list thumbnail and the detail-screen
3838
/// header. WebP roughly halves the bytes of an equivalent-quality JPEG
@@ -83,7 +83,7 @@ class UserImageStorage {
8383
return imagesDir;
8484
}
8585

86-
/// Reads `sourcePath`, re-encodes it to WebP (quality 80, longest
86+
/// Reads `sourcePath`, re-encodes it to WebP (quality 80, shortest
8787
/// edge 1024px), writes the result to the matching images directory
8888
/// under `<ownerId>.webp`, and returns the relative slug to persist.
8989
/// The source file is left untouched.
@@ -185,9 +185,12 @@ class UserImageStorage {
185185
quality: 80,
186186
minWidth: 1024,
187187
minHeight: 1024,
188-
// `minWidth`/`minHeight` are upper bounds for the *longest*
189-
// edge when the source exceeds them; the compressor preserves
190-
// aspect ratio. Shorter-edge images pass through untouched.
188+
// `minWidth`/`minHeight` bound the *shortest* edge, not the longest.
189+
// The compressor takes `min(width/minWidth, height/minHeight)` as its
190+
// scale factor, so the smaller ratio wins and the edge that lands on
191+
// 1024 is the short one: a 4080x3072 frame comes out 1360x1024, and a
192+
// 16:9 frame wider still. Aspect ratio is preserved and an image
193+
// already smaller than 1024 on both edges passes through untouched.
191194
);
192195
} catch (_) {
193196
return null;

lib/features/add_meal/util/meal_photo_encoder.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ enum MealPhotoFormat {
5353
/// the documents directory is a photo the export zip picks up and the user
5454
/// never asked to keep.
5555
///
56-
/// The encoding — quality 80, longest edge 1024 px — matches
56+
/// The encoding — quality 80, shortest edge 1024 px — matches
5757
/// `UserImageStorage` on purpose. It is the pipeline this app already trusts
5858
/// for food photography, and a 1024 px image is about 1400 visual tokens,
5959
/// which is what makes this cost fractions of a cent where anyone is charging
@@ -193,9 +193,12 @@ class MealPhotoEncoder {
193193
// the app promises it asks for no location. [PhotoMetadata] does the
194194
// same job on the fallback path below, where no encoder runs.
195195
keepExif: false,
196-
// `minWidth`/`minHeight` are upper bounds for the *longest* edge
197-
// when the source exceeds them; the compressor preserves aspect
198-
// ratio. Shorter-edge images pass through untouched.
196+
// `minWidth`/`minHeight` bound the *shortest* edge, not the longest.
197+
// The compressor takes `min(width/minWidth, height/minHeight)` as its
198+
// scale factor, so the smaller ratio wins and the edge that lands on
199+
// 1024 is the short one: a 4080x3072 frame comes out 1360x1024, and a
200+
// 16:9 frame wider still. Aspect ratio is preserved and an image
201+
// already smaller than 1024 on both edges passes through untouched.
199202
);
200203
} catch (_) {
201204
return null;

lib/features/recipes/presentation/screens/recipe_builder_screen.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,8 @@ class _RecipeBuilderScreenState extends State<RecipeBuilderScreen> {
408408
try {
409409
final picker = ImagePicker();
410410
// Pick at full resolution — `UserImageStorage.importFrom` re-encodes
411-
// to WebP at quality 80 with a 1024px longest-edge cap, so we don't
412-
// need image_picker's own JPEG compression on top. Doing it in one
411+
// to WebP at quality 80, bounding the shortest edge at 1024px, so we
412+
// don't need image_picker's own JPEG compression on top. Doing it in one
413413
// place keeps the on-disk footprint consistent regardless of which
414414
// source (camera / gallery) the photo came from.
415415
final picked = await picker.pickImage(source: source);

0 commit comments

Comments
 (0)