npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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_DENIED
  • CAMERA_NOT_AVAILABLE
  • PREVIEW_NOT_RUNNING
  • PREVIEW_PAUSED
  • CAPTURE_IN_PROGRESS
  • CAPTURE_FAILED
  • FOCUS_FAILED
  • FOCUS_NOT_SUPPORTED
  • PREVIEW_SYSTEM_SUSPENDED
  • PREVIEW_RESTORE_FAILED
  • INVALID_OPTIONS
  • INVALID_CROP_OPTIONS
  • SETTINGS_OPEN_FAILED
  • DEVICE_NOT_FOUND
  • DEVICE_SELECTION_NOT_SUPPORTED
  • UNSUPPORTED_OUTPUT
  • PROCESSING_FAILED
  • WEB_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();
  • вспышка;
  • зум;
  • ручная экспозиция;
  • распознавание документов;
  • сложные фильтры;
  • публичные события.