Scope and stack
Version 1 turns one portrait at a time into a Korean passport, driver's license, or resident registration card photo, or a general 35×45 mm or 30×40 mm photo. It saves a single JPEG or a 4×6 inch print sheet to the gallery and can share a saved file. There is no account, server, or photo archive; one job is edited at a time and nothing is kept between jobs.
| UI | Kotlin and Jetpack Compose, minSdk 29, targetSdk 36 |
|---|---|
| Input | CameraX capture (front or back camera, optional 3-second timer) or Android Photo Picker, images only |
| Face check | Bundled ML Kit Face Detection, fast mode with eye-open classification |
| Background | Bundled ML Kit Selfie Segmentation, single-image mode, general photos only |
| Output | 300 dpi JPEG through MediaStore to Pictures/DotoriIdPhoto, share sheet for saved files |
| Ads | AdMob banner on the home screen only, with UMP consent |
The two ML Kit wrappers live in the shared libs/ai tree as face-detection and selfie-segmentation. They own only the SDK call, its release, and a neutral result: a face box normalized to the upright image with roll, yaw, and eye-open probabilities, or a foreground confidence mask. Sizes, cropping, confirmations, and output belong to the app. The app does not depend on the OCR-side libs/ai/core used by Dotori Business Card Scanner.
Official sizes and what the app checks
The presets come from the Korean government guidance checked on 23 September 2026. The app treats the physical size and file limits as rules it can enforce and everything about the person in the photo as rules the user checks.
| Passport | 35×45 mm; online file JPEG up to 500 KB, 413×531 px recommended; white original background; crown (excluding hair) to chin 32–36 mm. Source: Ministry of Foreign Affairs photo rules and online application. |
|---|---|
| Driver's license | 35×45 mm; current guidance for online files is JPG up to 500 KB at 413×531 px; the photo must not be edited or composited. Source: Korea Road Traffic Authority. |
| Resident registration card | 35×45 mm; white background recommended, a pale single color allowed. A separate online file rule was not confirmed, so the app offers the single file and the print sheet without a byte limit. Source: Ministry of the Interior and Safety. |
| General | 35×45 mm or 30×40 mm. App convenience presets, not an official standard. |
- Checked by the app. The aspect ratio of the chosen purpose, that the crop has at least the output pixel count, the output pixel size and 300 dpi tag, and the 500,000-byte limit for passport and license single files.
- Checked by the user. Framing and margins, head size, pose, eyes, lighting, background, and how recent the photo is. The help screen states the passport crown-to-chin rule and links to the passport photo guide.
- Not done. The app does not measure head length, does not decide acceptance, and does not guarantee that an office will accept a photo. It also does not assume a unified standard across the three IDs, because none was confirmed.
Input and the face check
The purpose is chosen first, because it decides the aspect ratio, the initial frame, and whether background tools exist. Changing the purpose starts a new job.
- A picked image is copied to a temporary file in the app cache, with a 32 MiB limit, and the copy is deleted right after decoding. A camera capture is written to the same cache folder and deleted after decoding. The picker URI is not stored.
ImageDecoderapplies the EXIF rotation and mirroring to the pixels, converts to sRGB, and limits the long side to 2048 px. Images with a side over 30,000 px are rejected before decoding.- Face detection runs on a copy whose long side is at most 768 px. The detector does not use contour mode, because contour detection reports only the most prominent face and would hide a second person.
- Editing starts only when exactly one face is found. No face, several faces, or a detector failure all keep the user on the home screen with a message; a photo whose face could not be checked is treated as unchecked, not as passing.
- A result that arrives after the user cancelled is not applied, and a second operation cannot start while one is running.
The camera screen also runs the detector on a preview frame at most every 500 ms and shows guidance such as “Only one person in the frame” or “Face forward and center your head”. That guidance and a pose or closed-eye warning on the edit screen are advice only; they never block capture or saving.
The camera permission is requested only when the user chooses to take a photo, and choosing a photo needs no storage or media permission. The manifest's only app-declared permission is CAMERA, with the camera marked as not required.
Crop frame and initial framing
The top and bottom lines on the preview are the edges of the photo that will be saved, not measurement lines. Earlier builds drew estimated crown and chin lines and offered an automatic framing button; both were removed after user feedback.
- Edge handles. Dragging one of the two handles on the right moves that edge while the opposite edge stays fixed, and the width follows from the purpose's aspect ratio. The frame cannot extend past the source image, cannot be flipped, and cannot shrink below 15% of the largest possible frame.
- Moving the frame. Dragging inside the frame moves it in any direction at the same size. Movement is counted from the start of the drag, so after the frame stops at a photo edge and the finger comes back, frame and finger stay aligned. A tap or jitter under the touch slop is not a drag and does not clear the framing confirmation.
- Hit test. Whether a touch grabs a handle or the frame is decided from the model state at touch-down, not from the last drawn frame, because a new touch can arrive before recomposition.
- Visibility. The saved-photo frame is a white line with a dark rim so it shows on white walls and dark shade, and it thickens while dragging. The source image has its own thin outline.
The face box is used for one thing only: where the handles start. The head is estimated as 1.3 times the face box height, because ML Kit's box runs from roughly the forehead to the chin. The head is placed to fill a purpose-specific share of the photo height: 34/45 (about 76%) for the three ID purposes, the middle of the passport 32–36 mm rule, and 60% for general photos, which leaves room for the shoulders. The remaining height is split one part above the crown to two parts below the chin, and the frame is centered horizontally on the face.
The 1.3 factor is an unverified estimate. It has not been checked against real portraits, and clamping to the source trims one margin when the photo is short or the face is near an edge. That is why the face box is never used to draw guide lines, decide pass or fail, or block saving. The “one fist above, two fists below” rule is used only as a ratio; using a real fist size would make the head about 45% of the height, well under the passport rule.
The edge handles expose a progress value and a label for accessibility services. Moving the frame sideways has no separate accessible control yet.
Backgrounds
The passport rules forbid removing the background or compositing a white one, and the driver’s license guidance forbids edited or composited photos; the app applies the same rule to the resident registration card as its own policy. For all three ID purposes, the background tools are not shown and the saved file is always rendered from the original pixels.
- General photos offer three background chips: original, white, and a light blue.
- Selfie Segmentation runs on the 768 px analysis copy and returns a raw confidence mask. The mask is scaled to the source and used as alpha to lay the person over the solid color; the original bitmap is kept unchanged, so choosing “original” again restores it exactly.
- The mask is computed once per photo and reused when the color changes. If segmentation fails, the original stays and the user can retry.
- Changing the background clears the “Background edges checked” confirmation, because hair and shoulder edges are for the user to check.
Save gate and output files
The save button and the message above it come from one function, saveBlocker, so the button is never enabled for a reason the message does not explain. It checks, in order: no photo, an operation in progress, framing not confirmed, rules or background edges not confirmed, and a crop smaller than the output size. Several faces are not a save reason, because such photos never reach editing. Moving an edge or the frame clears the framing confirmation; the rules confirmation stays.
| Pixel size | mm ÷ 25.4 × 300, rounded: 413×531 px for 35×45 mm and 354×472 px for 30×40 mm |
|---|---|
| Scaling | Downscale only. A crop with fewer pixels than the output is blocked as low resolution instead of being enlarged to hide it. |
| JPEG | Quality 95 down to 60 in steps of 5, checking the real encoded size. Passport and license single files must be 500,000 bytes or less; if even quality 60 is too large, saving fails. |
| Metadata | A new EXIF block with only 300 dpi resolution and upright orientation. Nothing is copied from the source, so GPS and camera metadata never reach the output. |
| Print sheet | 1200×1800 px (4×6 inch at 300 dpi): six 35×45 mm photos or nine 30×40 mm photos, 24 px apart, with gray corner marks and a light gray cut line one pixel outside each photo. The photo pixels are unchanged, and single files have no border. |
- Files are written as a pending MediaStore row, published only after the whole file is written, and the row is deleted if anything fails. The source image is never overwritten.
- File names contain a timestamp and the file kind, not a name. A save cannot be started twice at once, and a save in progress cannot be cancelled halfway.
- Sharing sends the saved MediaStore URI with a read grant to the app the user picks. The gallery button opens the device gallery app and works regardless of the save state.
- Print sheets must be printed at actual size, without scaling or automatic cropping; the app says so in its help.
Storage, privacy, and backup
A portrait is personal data about whoever is in it, so the app keeps no copy of its own.
- The photo being edited lives in memory in the view model. It survives rotation but not process death; the next start begins with no photo.
- Temporary files in the app cache are cleared when the view model starts, when the purpose changes, after a save, and when the view model is cleared.
- The manifest sets
allowBackup="false"andfullBackupContent="false", anddataExtractionRulesexcludes all app storage from cloud backup and device-to-device transfer. - Face detection and segmentation use models bundled in the app. Photos are not uploaded for processing and are not attached to ad requests. ML Kit's own diagnostics and the ad SDK have separate network activity, described in the privacy policy.
- Files saved to the gallery belong to the gallery. The app does not control whether a gallery or cloud photo service backs them up, and deleting them later does not recall copies already shared.
- There is no generative AI feature, no beauty filter, and no retouching or tone change.
Verification and known limits
Crop geometry (portrait and landscape sources, edge drags, frame moves, print layout, and initial framing near image edges) and the save gate are covered by JVM unit tests. Three instrumented tests on an Android 11 test phone check output pixel size, dpi, and byte limits, EXIF rotation with GPS removal, MediaStore publishing, and that background composition leaves the foreground and the original unchanged.
Measured on the device. Saved 35×45 mm single files read back as 413×531 px at 300 dpi, at 15,042 and 43,355 bytes. On the same phone, dragging the top handle kept the bottom edge fixed and the reverse, the save button stayed at the same position while the adjustment area scrolled, and the gallery button opened the Samsung gallery. The test captures had no person in them, so they say nothing about recognition rates or acceptance.
- Completing a selection in the system photo picker has been checked only up to the picker opening.
- First-run offline processing and segmentation edge quality on real people are not yet measured, and nothing has been measured on a second device.
- Rotation and background transitions during a job have not been measured on a device.
- The 1.3 head factor is an estimate, so the initial frame may need adjustment on every photo.
- Only Korean sizes are included. Foreign passport and visa sizes, a photo archive, and ML Kit Subject Segmentation are outside version 1.
- Moving the frame sideways has no accessible alternative to dragging.