@virusv/capacitor-camera-multitool
v1.1.0
Published
Создание фото, видео, сканирование QR, сжатие и обрезка фото
Downloads
211
Readme
Capacitor Camera MultiTool
Capacitor Camera MultiTool - плагин Capacitor 8 для встроенного live preview камеры, фокусировки по нажатию, создания фото и обработки результата: crop, resize и JPEG/PNG-сжатие.
Первая версия не открывает системную камеру и не использует галерею. Основной результат capturePhoto() - обработанное изображение в base64.
Установка
npm install @virusv/capacitor-camera-multitool
npx cap syncРазрешения
В первой версии используется только доступ к камере.
Android manifest плагина объявляет:
<uses-permission android:name="android.permission.CAMERA" />iOS-приложение должно добавить privacy key:
<key>NSCameraUsageDescription</key>
<string>Нужен доступ к камере для preview и создания фото.</string>Storage/photo library, microphone и location не используются. Они могут понадобиться только будущим сценариям video, gallery, QR или EXIF location.
Если пользователь запретил камеру, приложение может вызвать openAppSettings(). На Android и iOS метод открывает настройки приложения, на Web возвращает WEB_API_NOT_SUPPORTED.
Быстрый старт
import { CapacitorCameraMultiToolPlugin } from '@virusv/capacitor-camera-multitool';
await CapacitorCameraMultiToolPlugin.requestPermissions();
await CapacitorCameraMultiToolPlugin.startPreview({
parent: 'preview',
position: 'rear',
width: 320,
height: 320,
previewMirror: 'auto',
});
const focusSupport = await CapacitorCameraMultiToolPlugin.getFocusSupport();
if (focusSupport.isSupported) {
const focus = await CapacitorCameraMultiToolPlugin.focus({ x: 0.5, y: 0.5 });
console.log(focus.isFocusSuccessful);
}
const photo = await CapacitorCameraMultiToolPlugin.capturePhoto({
output: 'base64',
processing: {
crop: { aspectRatio: '1:1' },
maxWidth: 1200,
maxHeight: 1200,
compressionQuality: 90,
format: 'jpeg',
},
});
await CapacitorCameraMultiToolPlugin.stopPreview();Методы
| Метод | Назначение |
| --- | --- |
| checkPermissions() | Возвращает { camera }. |
| requestPermissions() | Запрашивает только camera permission. |
| openAppSettings() | Открывает настройки приложения на Android/iOS. |
| getAvailableDevices() | Возвращает доступные камеры как optional advanced mode. |
| startPreview(options?) | Запускает live preview. Повторный вызов идемпотентен. |
| pausePreview() | Ставит preview на визуальную паузу с последним кадром. |
| resumePreview() | Возобновляет preview после паузы. |
| stopPreview() | Освобождает ресурсы камеры. Повторный вызов безопасен. |
| getPreviewState() | Возвращает состояние и фактическую геометрию preview. |
| setPreviewSize(options) | Меняет x, y, width, height после старта. |
| getFocusSupport() | Возвращает возможность ручного оптического фокуса активной камеры. |
| focus(options) | Выполняет фокусировку по нормализованным координатам 0..1 и возвращает её результат. |
| capturePhoto(options?) | Делает фото из preview и возвращает обработанный результат. |
Имя плагина в Web, Android, iOS и документации: CapacitorCameraMultiToolPlugin.
Preview
x, y, width, height задаются в CSS/logical pixels относительно WebView viewport. Web применяет их как CSS-геометрию, Android конвертирует в физические пиксели, iOS использует points.
setPreviewSize() меняет layout без полной пересборки camera session, если платформа это позволяет. getPreviewState() возвращает isRunning, isPaused, isSystemSuspended, position, deviceId, x, y, width, height.
previewMirror: 'auto' зеркалит preview фронтальной камеры. Это не меняет итоговое фото: для него отдельно используется processing.photoMirror, по умолчанию off.
Ручной фокус
После startPreview() вызовите getFocusSupport(). Результат относится к активной камере: на iOS он зависит от возможностей устройства, на Android дополнительно проверяются режимы автофокуса и доступность AF-регионов через Camera2, а в Web всегда возвращается { isSupported: false }.
При isSupported: true вызов focus({ x, y }) ждёт завершения операции и возвращает { isFocusSuccessful }. На Android значение берётся из FocusMeteringResult; ошибки запуска, отмены или выполнения операции возвращаются как FOCUS_FAILED. На iOS результат true означает, что AVFoundation принял настройку автофокуса: отдельный показатель качества фокусировки эта платформа не предоставляет. Если ручной фокус недоступен на нативной платформе, focus() возвращает FOCUS_NOT_SUPPORTED.
const { isSupported } = await CapacitorCameraMultiToolPlugin.getFocusSupport();
if (isSupported) {
const { isFocusSuccessful } = await CapacitorCameraMultiToolPlugin.focus({ x: 0.3, y: 0.6 });
if (!isFocusSuccessful) {
// Камера завершила операцию, но не смогла подтвердить фокус.
}
}Пресет iOS camera session
startPreview() на iOS по умолчанию использует iosSessionPreset: 'high' вместо высокоразрешённого 'photo'. Это уменьшает объём данных, который нужно декодировать и сжимать после съёмки. При необходимости пресет можно выбрать до запуска preview:
await CapacitorCameraMultiToolPlugin.startPreview({
parent: 'preview',
position: 'rear',
iosSessionPreset: 'hd1280x720',
});Доступные значения: 'photo', 'high', 'medium', 'low', 'hd1280x720', 'hd1920x1080', 'hd4K3840x2160', 'vga640x480', 'cif352x288', 'iFrame960x540', 'iFrame1280x720'.
Плагин проверяет выбранное значение через canSetSessionPreset; неподдерживаемый выбранной камерой режим вернёт INVALID_OPTIONS. Фиксированные video preset’ы не гарантируют точное разрешение итогового фото — это зависит от камеры и AVCapturePhotoOutput. Параметр применяется только при создании новой iOS session, а Android и Web его игнорируют. 'inputPriority' намеренно не добавлен: он требует отдельного выбора activeFormat устройства.
Качество снимка Android
На Android startPreview() по умолчанию использует androidImageCapturePreset: 'high'. Для ImageCapture это задаёт целевой размер 1600×1200, вместо используемого CameraX по умолчанию максимального доступного размера. Меньший исходный кадр заметно сокращает время декодирования, crop, resize и JPEG/PNG-сжатия.
await CapacitorCameraMultiToolPlugin.startPreview({
position: 'rear',
androidImageCapturePreset: 'medium',
});Доступные значения: 'photo' — максимальное доступное разрешение; 'high' — целевой размер 1600×1200 (по умолчанию); 'medium' — 1280×960; 'low' — 640×480. Камеры поддерживают разные наборы размеров, поэтому CameraX выберет ближайший доступный размер, предпочитая меньший. Параметр применяется только при создании нового preview и игнорируется на iOS и Web.
Выбор камеры
Базовый сценарий - position: 'rear' | 'front', по умолчанию rear.
getAvailableDevices() возвращает deviceId, label, примерный position и isDefault. deviceId является техническим идентификатором платформы или браузера и не должен храниться как стабильная пользовательская настройка без повторной проверки.
Фото, crop, resize и compression
capturePhoto() по умолчанию возвращает:
{
output: 'base64',
format: 'jpeg',
mimeType: 'image/jpeg',
width: 1200,
height: 1200,
size: 180000,
base64: '...'
}processing.crop поддерживает:
false- не обрезать;true- center crop, aspect ratio берётся изmaxWidth/maxHeight;{ mode: 'center', aspectRatio: '1:1' };{ mode: 'position', anchor: 'topLeft', aspectRatio: '4:3' };{ mode: 'focusPoint', point: { x: 0.5, y: 0.5 }, aspectRatio: '1:1' };{ mode: 'customRect', rect: { x, y, width, height } }.
maxWidth и maxHeight уменьшают результат без увеличения маленьких изображений. compressionQuality - сила JPEG-сжатия 1..100; для PNG игнорируется.
Для больших снимков задавайте maxWidth, maxHeight и compressionQuality: base64 увеличивает объём данных примерно на треть.
Стратегия съёмки Web
capturePhoto() на Web принимает webCaptureMode непосредственно для создаваемого снимка:
'photo'— значение по умолчанию; используетImageCapture.takePhoto()для максимального качества;'previewFrame'— берёт текущий кадр<video>без вызоваtakePhoto(). Обычно работает быстрее, но ограничен фактическим разрешением preview.
await CapacitorCameraMultiToolPlugin.capturePhoto({
webCaptureMode: 'previewFrame',
processing: { maxWidth: 1200, maxHeight: 1200, format: 'jpeg' },
});Параметр применяется только в Web и игнорируется Android и iOS.
Необязательное сжатие через jsquash в Web
По умолчанию Web использует встроенные Canvas crop, resize и кодирование. Чтобы в отдельной сборке приложения включить WebAssembly-кодеки jsquash, установите их в приложение, а не в зависимости плагина:
npm install @jsquash/resize @jsquash/jpeg @jsquash/pngСоздайте адаптер один раз при запуске приложения:
import { encode as encodeJpeg } from '@jsquash/jpeg';
import { encode as encodePng } from '@jsquash/png';
import { init as initJpeg } from '@jsquash/jpeg/encode';
import { init as initPng } from '@jsquash/png/encode';
import resize, { initResize } from '@jsquash/resize';
import { configureWebImageCompressionAdapter, createJsquashImageCompressionAdapter } from '@virusv/capacitor-camera-multitool';
const jsquashAdapter = createJsquashImageCompressionAdapter(
{
resize,
encodeJpeg,
encodePng,
initResize,
initJpeg,
initPng,
},
{ resizeMethod: 'lanczos3', autoWarmup: true },
);autoWarmup: true начинает загрузку и инициализацию WASM resize, JPEG и PNG сразу после создания адаптера, не блокируя интерфейс. Так задержка переносится с первого снимка на момент инициализации приложения. Для ручного управления не передавайте autoWarmup и вызовите await jsquashAdapter.warmup() в подходящий момент, например после показа preview или во время простоя. Прогрев использует сеть и CPU; при его ошибке адаптер считается неподдерживаемым, а Web автоматически использует Canvas fallback.
Его можно зарегистрировать глобально до первого capturePhoto():
configureWebImageCompressionAdapter(jsquashAdapter);После регистрации адаптер применяется автоматически ко всем Web-вызовам capturePhoto(), в которых не указан свой processing.webImageCompressionAdapter:
const photo = await CapacitorCameraMultiToolPlugin.capturePhoto({
output: 'base64',
processing: {
maxWidth: 1200,
maxHeight: 1200,
compressionQuality: 90,
format: 'jpeg',
},
});Или передать только для одного снимка — глобальная регистрация для этого не нужна:
const photo = await CapacitorCameraMultiToolPlugin.capturePhoto({
output: 'base64',
processing: {
maxWidth: 1200,
maxHeight: 1200,
compressionQuality: 90,
format: 'jpeg',
webImageCompressionAdapter: jsquashAdapter,
},
});processing.webImageCompressionAdapter имеет приоритет над глобальным. Адаптер получает пиксели после встроенных crop и photoMirror, затем выполняет resize до maxWidth/maxHeight и кодирует JPEG или PNG. Он сначала проверяет доступность WebAssembly, ImageData и переданных функций jsquash. Если проверка не пройдена, загрузка WASM, resize, кодирование или результат адаптера завершатся ошибкой, текущий снимок будет обработан обычным Canvas-алгоритмом. Сбойный адаптер одного снимка не вызывается повторно до следующего stopPreview(), а глобальный — до следующего configureWebImageCompressionAdapter(...); проблемное устройство не будет пытаться использовать jsquash при каждом снимке.
Для собственной реализации передайте объект WebImageCompressionAdapter в configureWebImageCompressionAdapter или capturePhoto({ processing: { webImageCompressionAdapter } }). Его isSupported() должен проверять среду, а compress(input) вернуть { blob, width, height } с MIME-типом, совпадающим с input.format.
Lifecycle
Плагин различает пользовательскую паузу и системную паузу.
pausePreview()сохраняет последний кадр и оставляет preview в пользовательском состоянииpaused.- При background/inactive preview переводится в
systemSuspended. - При foreground
autoResume: trueвосстанавливает preview только если до системной паузы он былrunning. - Если пользователь поставил preview на паузу, foreground не снимает эту паузу.
- При закрытии страницы или экрана приложение должно вызвать
stopPreview(). - Публичные события в первой версии не добавляются.
Web
Web-реализация использует только navigator.mediaDevices.getUserMedia, <video> и <canvas>. Fallback на file input, gallery picker или <input capture> не используется.
Web требует secure context: HTTPS, localhost или доверенная WebView-среда. Если getUserMedia, live stream или canvas недоступны, метод возвращает WEB_API_NOT_SUPPORTED. Браузер не даёт унифицированного API управления оптическим фокусом: getFocusSupport() возвращает { isSupported: false }. Для обратной совместимости focus() в Web сохраняет точку для crop focusPoint, но возвращает { isFocusSuccessful: false }.
Ошибки
Ошибки имеют стабильный code и русское message.
Типовые коды:
CAMERA_PERMISSION_DENIEDCAMERA_NOT_AVAILABLEPREVIEW_NOT_RUNNINGPREVIEW_PAUSEDCAPTURE_IN_PROGRESSCAPTURE_FAILEDFOCUS_FAILEDFOCUS_NOT_SUPPORTEDPREVIEW_SYSTEM_SUSPENDEDPREVIEW_RESTORE_FAILEDINVALID_OPTIONSINVALID_CROP_OPTIONSSETTINGS_OPEN_FAILEDDEVICE_NOT_FOUNDDEVICE_SELECTION_NOT_SUPPORTEDUNSUPPORTED_OUTPUTPROCESSING_FAILEDWEB_API_NOT_SUPPORTED
Демо-приложение
example-app содержит ручную проверку на русском:
- permissions и настройки;
- список камер и выбор
deviceId; - start/pause/resume/stop preview;
setPreviewSize();- проверка поддержки и tap-to-focus с визуальным индикатором успешной операции;
- crop/resize/compression;
- история снимков текущей сессии.
UI построен на Ionic Framework через CDN, без добавления Ionic в package.json.
Что не входит в первую версию
- QR-сканирование;
- запись видео;
- выбор из галереи;
- сохранение в галерею;
deleteFile();- вспышка;
- зум;
- ручная экспозиция;
- распознавание документов;
- сложные фильтры;
- публичные события.
