Scanorea is a modern Android application for converting batches of photos into high-quality PDF documents quickly, safely, and efficiently. Built with Jetpack Compose and Material You (Material 3 Dynamic Color), Scanorea provides an in-app native PDF viewer, professional document scanning filters (such as high-contrast B&W thresholding), intelligent auto-compression profiles with predictive size estimations, and flexible Storage Access Framework (SAF) folder export, all while preserving user privacy and preventing Out-Of-Memory errors on high-resolution camera photos.
- Flexible Photo Import: Import multiple photos seamlessly via Android Photo Picker or directly capture pages with the camera without requiring runtime storage permissions.
- Single-Image Carousel Workspace: Swipe through pages one at a time using
HorizontalPagerwith real-time hardware-accelerated preview, page reordering (<-/->), and individual page deletion. - Document Scanner Filters with Fine-Tuning:
- Instant B&W on Import: Automatically applies midtone-anchored, high-contrast black-and-white thresholding to remove paper shadows while keeping faint text legible.
- Per-Page Sharpness & Brightness: Dedicated sliders for Sharpness/Contrast (0.5x - 2.5x) and Brightness (-50 to +50) with quick reset.
- Interactive Filter Chips: Quick-switch chips for Original Color, B&W Document, Grayscale, Magic Color (Enhanced), and a "To All" batch action.
- 4-Corner Perspective Correction & Auto Paper Detection:
- Automatic Edge Detection: Analyzes document luminosity gradients to locate 4 paper corners automatically without external heavyweight dependencies.
- Interactive Manual Adjustment: Visual overlay with 4 touch-draggable corner pins, geometric convex validation, and haptic feedback.
- Hardware-Accelerated Homography: Real-time perspective unwarping using projective matrix mapping and bilinear anti-aliased bitmap shaders.
- Conversion Presets with Automated Date Tokens:
- Save, load, and manage conversion presets (paper size, orientation, compression, name template).
- Dynamic date token evaluation: templates like
Rendra_23.XX.XXXX_Aljabar_{DD:MM:YYYY}automatically evaluate{DD:MM:YYYY}to today's date (e.g.,21-09-2026).
- Smart Auto-Compression Profiles: Choose optimal file sizes with real-time estimated file sizes before conversion:
- Auto Balanced (~250 KB/page): Recommended for general sharing and archiving.
- Small File (~100 KB/page): Highly compressed for email attachments and messaging.
- High Quality (~500 KB/page): Balanced detail for clear reading.
- Maximum / Original (~1.5 MB/page): Preserves full pixel resolution.
- Built-in Native PDF Viewer:
- Tight 8.dp page spacing with realistic paper drop shadows.
- Interactive touch-draggable Fast-Scroller thumb and track with
.systemGestureExclusion()to prevent edge back gesture conflicts. - Dynamic page-under-scrollbar detection: automatically updates the page indicator (
1 / N->2 / N) based on whichever paper sheet is currently intersecting the scrollbar thumb. - Smooth pinch-to-zoom and pan gestures using Android's native
android.graphics.pdf.PdfRenderer.
- Custom Save Location (SAF): Choose a custom destination folder via Storage Access Framework (
OpenDocumentTree) in the conversion options sheet or use "Save As" in the viewer. - Recent Documents Dashboard with Pinned Folders Grid:
- Google Photos-style 2x2 grid featuring up to 3 pinned categories plus an "Other" entry for complete folder management.
- Default pinned categories (
Favorites,Work,Study) out of the box for immediate utility. - Full-featured
AllFoldersBottomSheetdisplaying document counts, pin/unpin toggles (max 3 pinned limit), folder creation, renaming, and deletion. - Access, preview, share, and manage previously converted PDFs directly from the dashboard with category filtering.
- Dynamic Material You Design: Fully adapts to Android 12+ system wallpaper color accents (Monet engine) with support for Dark Theme and edge-to-edge system navigation.
- System-Colored App Icon and Launch Splash: Android 12+ uses the wallpaper-derived neutral and accent palette for both the adaptive launcher icon and launch splash, including light/dark variants. Android 13+ themed icons tint the monochrome layer, while older versions use the app's brand fallback colors.
Scanorea strictly adheres to the Deep Module, Feature Boundary, and Clean Architecture guidelines:
- Presentation Layer (UI): Pure Jetpack Compose components organized by feature boundaries (
app/ui/,features/home/ui/,features/editor/ui/,features/pdfviewer/ui/,features/recentpdfs/ui/,features/tools/ui/,features/settings/ui/). - Domain Layer: Clean, pure Kotlin business models and repository contracts (
features/*/domain/). - Data Layer: Concrete data sources, repositories, and platform integrations (
features/*/data/). - Core Layer: Shared cross-cutting concerns (
core/common/,core/designsystem/,core/filter/,core/format/,core/share/).
Scanorea/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/leshoraa/scanorea/
│ │ │ │ ├── app/
│ │ │ │ │ ├── navigation/
│ │ │ │ │ │ └── MainNavTab.kt
│ │ │ │ │ └── ui/
│ │ │ │ │ └── MainScreen.kt
│ │ │ │ ├── core/
│ │ │ │ │ ├── common/
│ │ │ │ │ │ └── ResourceResult.kt
│ │ │ │ │ ├── designsystem/
│ │ │ │ │ │ ├── Color.kt
│ │ │ │ │ │ ├── Theme.kt
│ │ │ │ │ │ └── Type.kt
│ │ │ │ │ ├── filter/
│ │ │ │ │ │ ├── ImageAnalyzer.kt
│ │ │ │ │ │ └── ImageFilterType.kt
│ │ │ │ │ ├── format/
│ │ │ │ │ │ ├── DateTimeFormatter.kt
│ │ │ │ │ │ └── FileSizeFormatter.kt
│ │ │ │ │ └── share/
│ │ │ │ │ └── PdfDocumentSharer.kt
│ │ │ │ ├── features/
│ │ │ │ │ ├── editor/
│ │ │ │ │ │ ├── domain/
│ │ │ │ │ │ │ ├── CropGestureCalculator.kt
│ │ │ │ │ │ │ ├── DocumentCornerDetector.kt
│ │ │ │ │ │ │ ├── PerspectiveWarpCalculator.kt
│ │ │ │ │ │ │ └── model/
│ │ │ │ │ │ │ ├── AdjustmentTool.kt
│ │ │ │ │ │ │ ├── DocumentQuad.kt
│ │ │ │ │ │ │ ├── EditorCategory.kt
│ │ │ │ │ │ │ ├── HsvColor.kt
│ │ │ │ │ │ │ └── PageAnnotation.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ ├── components/
│ │ │ │ │ │ │ ├── DiscardChangesDialog.kt
│ │ │ │ │ │ │ ├── EditorAdjustPanel.kt
│ │ │ │ │ │ │ ├── EditorAnnotatePanel.kt
│ │ │ │ │ │ │ ├── EditorBottomActionBar.kt
│ │ │ │ │ │ │ ├── EditorCanvasBadges.kt
│ │ │ │ │ │ │ ├── EditorCanvasPager.kt
│ │ │ │ │ │ │ ├── EditorCategoryTabBar.kt
│ │ │ │ │ │ │ ├── EditorColorPickerDialog.kt
│ │ │ │ │ │ │ ├── EditorCropOverlay.kt
│ │ │ │ │ │ │ ├── EditorCropPanel.kt
│ │ │ │ │ │ │ ├── EditorFiltersPanel.kt
│ │ │ │ │ │ │ ├── EditorPagesGridDialog.kt
│ │ │ │ │ │ │ ├── EditorPerspectiveOverlay.kt
│ │ │ │ │ │ │ ├── EditorReorderLiftedCard.kt
│ │ │ │ │ │ │ ├── PagePreviewTransformation.kt
│ │ │ │ │ │ │ └── RenameDocumentDialog.kt
│ │ │ │ │ │ └── EditorWorkspace.kt
│ │ │ │ │ ├── home/
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ ├── components/
│ │ │ │ │ │ │ ├── HomeHeroBanner.kt
│ │ │ │ │ │ │ └── QuickToolsSection.kt
│ │ │ │ │ │ └── HomeScreen.kt
│ │ │ │ │ ├── imagestopdf/
│ │ │ │ │ │ ├── data/
│ │ │ │ │ │ │ ├── ImageDecoderDataSource.kt
│ │ │ │ │ │ │ ├── PdfGeneratorDataSource.kt
│ │ │ │ │ │ │ └── repository/
│ │ │ │ │ │ │ └── PdfConversionRepositoryImpl.kt
│ │ │ │ │ │ ├── domain/
│ │ │ │ │ │ │ ├── model/
│ │ │ │ │ │ │ │ ├── CompressionProfile.kt
│ │ │ │ │ │ │ │ ├── ConversionProgress.kt
│ │ │ │ │ │ │ │ ├── ImageCropBounds.kt
│ │ │ │ │ │ │ │ ├── ImagePage.kt
│ │ │ │ │ │ │ │ ├── PdfConversionOptions.kt
│ │ │ │ │ │ │ │ ├── PdfConversionResult.kt
│ │ │ │ │ │ │ │ ├── PdfPageOrientation.kt
│ │ │ │ │ │ │ │ └── PdfPageSize.kt
│ │ │ │ │ │ │ └── repository/
│ │ │ │ │ │ │ └── PdfConversionRepository.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ ├── components/
│ │ │ │ │ │ │ ├── CaptureSourceBottomSheet.kt
│ │ │ │ │ │ │ ├── ConversionOptionsBottomSheet.kt
│ │ │ │ │ │ │ ├── ConversionProgressDialog.kt
│ │ │ │ │ │ │ └── ConversionSuccessDialog.kt
│ │ │ │ │ │ ├── ImagesToPdfUiState.kt
│ │ │ │ │ │ └── ImagesToPdfViewModel.kt
│ │ │ │ │ ├── pdfviewer/
│ │ │ │ │ │ ├── data/
│ │ │ │ │ │ │ └── PdfRendererDataSource.kt
│ │ │ │ │ │ ├── domain/
│ │ │ │ │ │ │ └── PdfScrollCalculator.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ ├── components/
│ │ │ │ │ │ │ └── PdfFastScroller.kt
│ │ │ │ │ │ └── PdfViewerScreen.kt
│ │ │ │ │ ├── presets/
│ │ │ │ │ │ ├── data/
│ │ │ │ │ │ │ └── PresetRepository.kt
│ │ │ │ │ │ ├── domain/
│ │ │ │ │ │ │ ├── TemplateDateEvaluator.kt
│ │ │ │ │ │ │ └── model/
│ │ │ │ │ │ │ └── ConversionPreset.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ └── PresetManagementScreen.kt
│ │ │ │ │ ├── recentpdfs/
│ │ │ │ │ │ ├── data/
│ │ │ │ │ │ │ ├── PdfThumbnailLoader.kt
│ │ │ │ │ │ │ └── RecentPdfsRepository.kt
│ │ │ │ │ │ ├── domain/model/
│ │ │ │ │ │ │ └── RecentPdf.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ ├── components/
│ │ │ │ │ │ │ ├── AllFoldersBottomSheet.kt
│ │ │ │ │ │ │ ├── DocumentFolderDialogs.kt
│ │ │ │ │ │ │ ├── FolderGridDragCalculator.kt
│ │ │ │ │ │ │ ├── FolderGridSection.kt
│ │ │ │ │ │ │ ├── PdfThumbnailPreview.kt
│ │ │ │ │ │ │ └── RecentPdfItemCard.kt
│ │ │ │ │ │ ├── RecentPdfsSection.kt
│ │ │ │ │ │ ├── RecentPdfsViewModel.kt
│ │ │ │ │ │ └── ResultsScreen.kt
│ │ │ │ │ ├── settings/
│ │ │ │ │ │ ├── data/
│ │ │ │ │ │ │ └── SettingsRepository.kt
│ │ │ │ │ │ └── ui/
│ │ │ │ │ │ └── SettingsScreen.kt
│ │ │ │ │ └── tools/
│ │ │ │ │ └── ui/
│ │ │ │ │ └── ToolsScreen.kt
│ │ │ │ └── MainActivity.kt
│ │ │ ├── res/
│ │ │ │ └── xml/
│ │ │ │ └── file_paths.xml
│ │ │ └── AndroidManifest.xml
│ │ └── test/
│ │ └── java/com/leshoraa/scanorea/
│ └── build.gradle.kts
├── docs/
│ └── adr/
│ ├── 0001-images-to-pdf-architecture.md
│ ├── 0002-perspective-correction-and-corner-detection.md
│ ├── 0003-pinned-folders-grid-navigation.md
│ └── 0004-folder-lifecycle-and-viewmodel-boundary-refactor.md
├── gradle/
│ └── libs.versions.toml
├── AI_RULES.md
└── README.md
- Android Studio Ladybug (2024.2) / Meerkat (2024.3) or newer.
- JDK 17 or newer (e.g., Android Studio bundled JBR).
- Android SDK 37 (compileSdk & targetSdk), minSdk 24 (Android 7.0 Nougat and above).
Clone the repository and open it in Android Studio:
git clone <repository-url>
cd ScanoreaAll file sharing paths are configured in app/src/main/res/xml/file_paths.xml and wired to Android's FileProvider in AndroidManifest.xml. No external API keys or cloud tokens are required; all processing runs 100% locally on the device.
- Launch Scanorea on your device.
- Tap Add Photos or Camera to import images into your document workspace.
- Every page automatically defaults to high-contrast B&W mode. Swipe across pages in the Carousel workspace, reorder pages with
<-/->, or delete individual pages. - Fine-tune contrast/sharpness and brightness with sliders, or switch filters (Color, B&W, Gray, Enhanced).
- Tap the floating Convert to PDF button.
- In the conversion options sheet, pick or save a Preset (with automatic date tokens like
{DD:MM:YYYY}), choose paper size, compression profile, and choose a custom destination folder via Storage Access Framework (OpenDocumentTree). - Tap Convert to PDF to create the document.
- Preview pages in the native viewer with tight 8.dp page gaps and a scrollbar counter, or share via the system share sheet.
This project uses Kotlin 2.0.21 and Jetpack Compose with Gradle 9.5 and Android Gradle Plugin 9.3.3.
Compile Kotlin code:
JAVA_HOME=/opt/android-studio/jbr ./gradlew compileDebugKotlinRun the local unit test suite:
JAVA_HOME=/opt/android-studio/jbr ./gradlew testDebugUnitTestGenerate a debug APK:
JAVA_HOME=/opt/android-studio/jbr ./gradlew assembleDebugThe APK artifact will be generated at app/build/outputs/apk/debug/app-debug.apk.
Install directly onto a connected physical Android device or emulator via ADB:
adb install -r app/build/outputs/apk/debug/app-debug.apkLaunch the main activity:
adb shell am start -n com.leshoraa.scanorea/.MainActivity- High memory usage on low-RAM devices: Choose the Small File or Auto Balanced compression profile to constrain maximum bitmap dimensions during two-pass decoding.
- ColorMatrix filter appearance: For dark shadows on scanned physical documents, ensure the page has good initial lighting; the B&W Doc filter applies a high-contrast offset (
-120f) and contrast multiplier (2.2f) to cleanly separate ink from paper.
- Password encryption and digital signature embedding are not supported because native
android.graphics.pdf.PdfDocumentis focused on raster-to-vector page output. - Input formats are restricted to formats supported by Android's
BitmapFactory(JPEG, PNG, WEBP, HEIC).