Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ jobs:
- name: Rename APK with version
run: |
cd example/build/app/outputs/flutter-apk/
mv app-release.apk document_scanner_example_v${{ steps.ver.outputs.VERSION }}.apk
mv app-release.apk DocumentScanner_v${{ steps.ver.outputs.VERSION }}.apk
ls -la *.apk

- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: document_scanner_example_v${{ steps.ver.outputs.VERSION }}-apk
path: example/build/app/outputs/flutter-apk/document_scanner_example_v${{ steps.ver.outputs.VERSION }}.apk
name: DocumentScanner_v${{ steps.ver.outputs.VERSION }}-apk
path: example/build/app/outputs/flutter-apk/DocumentScanner_v${{ steps.ver.outputs.VERSION }}.apk
if-no-files-found: error
retention-days: 30
4 changes: 2 additions & 2 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
- name: Rename APK with version
run: |
cd example/build/app/outputs/flutter-apk/
mv app-release.apk document_scanner_example_v${{ steps.get_version.outputs.VERSION }}.apk
mv app-release.apk DocumentScanner_v${{ steps.get_version.outputs.VERSION }}.apk
ls -la *.apk

- name: Generate release notes
Expand Down Expand Up @@ -139,7 +139,7 @@ jobs:
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
files: example/build/app/outputs/flutter-apk/document_scanner_example_v${{ steps.get_version.outputs.VERSION }}.apk
files: example/build/app/outputs/flutter-apk/DocumentScanner_v${{ steps.get_version.outputs.VERSION }}.apk
body_path: release_notes.md
name: Document Scanner v${{ steps.get_version.outputs.VERSION }}
tag_name: ${{ github.ref_name }}
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<application
android:label="example"
android:label="Document Scanner"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>
Binary file modified example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon/icon_background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon/icon_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
Expand Down Expand Up @@ -89,6 +97,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -197,6 +221,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
url: "https://pub.dev"
source: hosted
version: "0.14.4"
flutter_lints:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -311,6 +343,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1+1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
Expand Down Expand Up @@ -692,6 +732,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.1 <4.0.0"
flutter: ">=3.32.0"
11 changes: 11 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter_launcher_icons: ^0.14.1

flutter:
uses-material-design: true

# Launcher icon: a sheet of paper with a smartphone scanning it.
# Regenerate with: dart run flutter_launcher_icons
flutter_launcher_icons:
android: true
ios: false
image_path: "assets/icon/icon.png"
adaptive_icon_foreground: "assets/icon/icon_foreground.png"
adaptive_icon_background: "assets/icon/icon_background.png"
min_sdk_android: 21
148 changes: 148 additions & 0 deletions example/tool/make_icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Generate the Document Scanner launcher icon: a sheet of paper with a
smartphone scanning it (green scan brackets + beam)."""
from PIL import Image, ImageDraw
import sys

SS = 4 # supersample factor for anti-aliasing
S = 1024 # final size
W = S * SS # working size

BLUE_TOP = (30, 136, 229)
BLUE_BOT = (13, 71, 161)
WHITE = (255, 255, 255, 255)
LINE = (207, 216, 220, 255)
PHONE = (38, 50, 56, 255)
SCREEN = (236, 239, 241, 255)
LENS = (96, 125, 139, 255)
GREEN = (0, 230, 118, 255)


def px(v): # scale a 1024-space value into working space
return int(v * SS)


def vgradient_rounded(size, top, bottom, radius):
base = Image.new("RGBA", (size, size), (0, 0, 0, 0))
grad = Image.new("RGBA", (size, size))
gd = ImageDraw.Draw(grad)
for y in range(size):
t = y / (size - 1)
r = int(top[0] + (bottom[0] - top[0]) * t)
g = int(top[1] + (bottom[1] - top[1]) * t)
b = int(top[2] + (bottom[2] - top[2]) * t)
gd.line([(0, y), (size, y)], fill=(r, g, b, 255))
mask = Image.new("L", (size, size), 0)
ImageDraw.Draw(mask).rounded_rectangle(
[0, 0, size - 1, size - 1], radius=radius, fill=255
)
base.paste(grad, (0, 0), mask)
return base


def make_subject():
"""Subject (sheet + scanning phone) on a transparent WxW canvas, centered."""
canvas = Image.new("RGBA", (W, W), (0, 0, 0, 0))
cx = W // 2

# --- document (back), tilted left ---
dw, dh = px(430), px(560)
doc = Image.new("RGBA", (dw, dh), (0, 0, 0, 0))
dd = ImageDraw.Draw(doc)
dd.rounded_rectangle([0, 0, dw - 1, dh - 1], radius=px(26), fill=WHITE)
lx0, lx1 = px(58), dw - px(58)
ly, gap, lh = px(86), px(74), px(26)
for i in range(5):
x1 = lx1 if i != 4 else int(dw * 0.6)
dd.rounded_rectangle([lx0, ly, x1, ly + lh], radius=lh // 2, fill=LINE)
ly += gap
doc = doc.rotate(11, expand=True, resample=Image.BICUBIC)
canvas.alpha_composite(
doc, (cx - px(150) - doc.width // 2, W // 2 - px(70) - doc.height // 2)
)

# --- smartphone (front, tilted right) actively scanning ---
pw, ph = px(320), px(600)
phone = Image.new("RGBA", (pw, ph), (0, 0, 0, 0))
pd = ImageDraw.Draw(phone)
pd.rounded_rectangle([0, 0, pw - 1, ph - 1], radius=px(58), fill=PHONE)
m = px(20)
pd.rounded_rectangle(
[m, px(64), pw - m, ph - px(64)], radius=px(40), fill=SCREEN
)
pd.ellipse([pw // 2 - px(9), px(30), pw // 2 + px(9), px(48)], fill=LENS)

# green scan corner brackets + beam on the phone screen
sx0, sy0 = m + px(30), px(140)
sx1, sy1 = pw - m - px(30), ph - px(140)
t, blen = px(15), px(66)
pd.rectangle([sx0, sy0, sx0 + blen, sy0 + t], fill=GREEN)
pd.rectangle([sx0, sy0, sx0 + t, sy0 + blen], fill=GREEN)
pd.rectangle([sx1 - blen, sy0, sx1, sy0 + t], fill=GREEN)
pd.rectangle([sx1 - t, sy0, sx1, sy0 + blen], fill=GREEN)
pd.rectangle([sx0, sy1 - t, sx0 + blen, sy1], fill=GREEN)
pd.rectangle([sx0, sy1 - blen, sx0 + t, sy1], fill=GREEN)
pd.rectangle([sx1 - blen, sy1 - t, sx1, sy1], fill=GREEN)
pd.rectangle([sx1 - t, sy1 - blen, sx1, sy1], fill=GREEN)
by = (sy0 + sy1) // 2
pd.rectangle([sx0, by - px(5), sx1, by + px(5)], fill=(0, 230, 118, 200))

phone = phone.rotate(-11, expand=True, resample=Image.BICUBIC)
canvas.alpha_composite(
phone, (cx + px(150) - phone.width // 2, W // 2 + px(80) - phone.height // 2)
)
return canvas


def fit_subject(subject, frac):
"""Return a WxW canvas with the subject scaled to occupy `frac` of width."""
bbox = subject.getbbox()
cropped = subject.crop(bbox)
target_w = int(W * frac)
scale = target_w / cropped.width
target_h = int(cropped.height * scale)
if target_h > int(W * frac):
scale = int(W * frac) / cropped.height
target_w = int(cropped.width * scale)
target_h = int(W * frac)
resized = cropped.resize((target_w, target_h), Image.LANCZOS)
out = Image.new("RGBA", (W, W), (0, 0, 0, 0))
out.alpha_composite(resized, ((W - target_w) // 2, (W - target_h) // 2))
return out


def main():
legacy_path, fg_path, bg_path = sys.argv[1], sys.argv[2], sys.argv[3]
subject = make_subject()

# Legacy full icon: gradient rounded-square bg + subject
bg = vgradient_rounded(W, BLUE_TOP, BLUE_BOT, radius=px(180))
legacy = bg.copy()
legacy.alpha_composite(fit_subject(subject, 0.74))
legacy.resize((S, S), Image.LANCZOS).save(legacy_path)

# Adaptive foreground: subject fills most of the drawable; the adaptive
# XML adds a 16% inset for the safe zone (0.90 * 0.68 ≈ 0.61 of the icon).
fit_subject(subject, 0.90).resize((S, S), Image.LANCZOS).save(fg_path)

# Adaptive background: full-bleed gradient (launcher applies the mask)
fullbg = Image.new("RGBA", (W, W))
gd = ImageDraw.Draw(fullbg)
for y in range(W):
t = y / (W - 1)
gd.line(
[(0, y), (W, y)],
fill=(
int(BLUE_TOP[0] + (BLUE_BOT[0] - BLUE_TOP[0]) * t),
int(BLUE_TOP[1] + (BLUE_BOT[1] - BLUE_TOP[1]) * t),
int(BLUE_TOP[2] + (BLUE_BOT[2] - BLUE_TOP[2]) * t),
255,
),
)
fullbg.resize((S, S), Image.LANCZOS).save(bg_path)

print("wrote", legacy_path, fg_path, bg_path)


if __name__ == "__main__":
main()
11 changes: 10 additions & 1 deletion lib/src/ui/document_camera_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,23 @@ class _DocumentCameraScreenState extends State<DocumentCameraScreen>
}

Widget _buildCameraView() {
// `controller.value.aspectRatio` is the sensor ratio in *landscape*
// (>= 1). On a portrait screen we must invert it, otherwise the preview is
// forced into a wide/short box: small and horizontally squashed.
final sensorRatio = _controller!.value.aspectRatio;
final previewRatio =
MediaQuery.of(context).orientation == Orientation.portrait
? 1 / sensorRatio
: sensorRatio;

return Stack(
fit: StackFit.expand,
children: [
// Camera preview and guide overlay share the same coordinate space
// so that fractional corner positions map directly to the captured image.
Center(
child: AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
aspectRatio: previewRatio,
child: Stack(
fit: StackFit.expand,
children: [
Expand Down
Loading