UNPKG

react-native-vision-camera

Version:

A powerful, high-performance React Native Camera library.

244 lines (225 loc) 10.4 kB
import type { AutoFocusSystem, CameraDevice, CameraDeviceFormat, VideoStabilizationMode } from '../types/CameraDevice' import { CameraRuntimeError } from '../CameraError' interface Size { width: number height: number } export interface FormatFilter { /** * The target resolution of the video (and frame processor) output pipeline. * If no format supports the given resolution, the format closest to this value will be used. */ videoResolution?: Size | 'max' /** * The target resolution of the photo output pipeline. * If no format supports the given resolution, the format closest to this value will be used. */ photoResolution?: Size | 'max' /** * The target aspect ratio of the video (and preview) output, expressed as a factor: `width / height`. * (Note: Cameras are in landscape orientation) * * In most cases, you want this to be as close to the screen's aspect ratio as possible (usually ~9:16). * * @example * ```ts * const screen = Dimensions.get('screen') * targetVideoAspectRatio: screen.height / screen.width * ``` */ videoAspectRatio?: number /** * The target aspect ratio of the photo output, expressed as a factor: `width / height`. * (Note: Cameras are in landscape orientation) * * In most cases, you want this to be the same as `targetVideoAspectRatio`, which you often want * to be as close to the screen's aspect ratio as possible (usually ~9:16) * * @example * ```ts * const screen = Dimensions.get('screen') * targetPhotoAspectRatio: screen.height / screen.width * ``` */ photoAspectRatio?: number /** * The target FPS you want to record video at. * If the FPS requirements can not be met, the format closest to this value will be used. */ fps?: number | 'max' /** * The target video stabilization mode you want to use. * If no format supports the target video stabilization mode, the best other matching format will be used. */ videoStabilizationMode?: VideoStabilizationMode /** * Whether you want to find a format that supports Photo HDR. */ photoHdr?: boolean /** * Whether you want to find a format that supports Photo HDR. */ videoHdr?: boolean /** * The target ISO value for capturing photos. * Higher ISO values tend to capture sharper photos, at the cost of reduced capture speed. * Lower ISO values tend to capture photos quicker. */ iso?: number | 'max' | 'min' /** * The target auto-focus system. * While `phase-detection` is generally the best system available, * you might want to choose a different auto-focus system. */ autoFocusSystem?: AutoFocusSystem } type FilterWithPriority<T> = { target: Exclude<T, null | undefined> priority: number } type FilterMap = { [K in keyof FormatFilter]: FilterWithPriority<FormatFilter[K]> } function filtersToFilterMap(filters: FormatFilter[]): FilterMap { return filters.reduce<FilterMap>((map, curr, index) => { for (const key in curr) { // @ts-expect-error keys are untyped map[key] = { // @ts-expect-error keys are untyped // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment target: curr[key], priority: filters.length - index, } } return map }, {}) } /** * Get the best matching Camera format for the given device that satisfies your requirements using a sorting filter. By default, formats are sorted by highest to lowest resolution. * * The {@linkcode filters | filters} are ranked by priority, from highest to lowest. * This means the first item you pass will have a higher priority than the second, and so on. * * @param device The Camera Device you're currently using * @param filters The filters you want to use. The format that matches your filter the closest will be returned * @returns The format that matches your filter the closest. * * @example * ```ts * const format = getCameraFormat(device, [ * { videoResolution: { width: 3048, height: 2160 } }, * { fps: 60 } * ]) * ``` */ export function getCameraFormat(device: CameraDevice, filters: FormatFilter[]): CameraDeviceFormat { // Combine filters into a single filter map for constant-time lookup const filter = filtersToFilterMap(filters) let bestFormat = device.formats[0] if (bestFormat == null) throw new CameraRuntimeError('device/invalid-device', `The given Camera Device (${device.id}) does not have any formats!`) // Compare each format using a point scoring system for (const format of device.formats) { let leftPoints = 0 let rightPoints = 0 // Video Resolution if (filter.videoResolution != null) { const leftVideoResolution = bestFormat.videoWidth * bestFormat.videoHeight const rightVideoResolution = format.videoWidth * format.videoHeight if (filter.videoResolution.target === 'max') { // We just want the maximum resolution if (leftVideoResolution > rightVideoResolution) leftPoints += filter.videoResolution.priority if (rightVideoResolution > leftVideoResolution) rightPoints += filter.videoResolution.priority } else { // Find video resolution closest to the filter (ignoring orientation) const targetResolution = filter.videoResolution.target.width * filter.videoResolution.target.height const leftDiff = Math.abs(leftVideoResolution - targetResolution) const rightDiff = Math.abs(rightVideoResolution - targetResolution) if (leftDiff < rightDiff) leftPoints += filter.videoResolution.priority if (rightDiff < leftDiff) rightPoints += filter.videoResolution.priority } } // Photo Resolution if (filter.photoResolution != null) { const leftPhotoResolution = bestFormat.photoWidth * bestFormat.photoHeight const rightPhotoResolution = format.photoWidth * format.photoHeight if (filter.photoResolution.target === 'max') { // We just want the maximum resolution if (leftPhotoResolution > rightPhotoResolution) leftPoints += filter.photoResolution.priority if (rightPhotoResolution > leftPhotoResolution) rightPoints += filter.photoResolution.priority } else { // Find closest photo resolution to the filter (ignoring orientation) const targetResolution = filter.photoResolution.target.width * filter.photoResolution.target.height const leftDiff = Math.abs(leftPhotoResolution - targetResolution) const rightDiff = Math.abs(rightPhotoResolution - targetResolution) if (leftDiff < rightDiff) leftPoints += filter.photoResolution.priority if (rightDiff < leftDiff) rightPoints += filter.photoResolution.priority } } // Find closest aspect ratio (video) if (filter.videoAspectRatio != null) { const leftAspect = bestFormat.videoWidth / bestFormat.videoHeight const rightAspect = format.videoWidth / format.videoHeight const leftDiff = Math.abs(leftAspect - filter.videoAspectRatio.target) const rightDiff = Math.abs(rightAspect - filter.videoAspectRatio.target) if (leftDiff < rightDiff) leftPoints += filter.videoAspectRatio.priority if (rightDiff < leftDiff) rightPoints += filter.videoAspectRatio.priority } // Find closest aspect ratio (photo) if (filter.photoAspectRatio != null) { const leftAspect = bestFormat.photoWidth / bestFormat.photoHeight const rightAspect = format.photoWidth / format.photoHeight const leftDiff = Math.abs(leftAspect - filter.photoAspectRatio.target) const rightDiff = Math.abs(rightAspect - filter.photoAspectRatio.target) if (leftDiff < rightDiff) leftPoints += filter.photoAspectRatio.priority if (rightDiff < leftDiff) rightPoints += filter.photoAspectRatio.priority } // Find closest max FPS if (filter.fps != null) { if (filter.fps.target === 'max') { if (bestFormat.maxFps > format.maxFps) leftPoints += filter.fps.priority if (format.maxFps > bestFormat.maxFps) rightPoints += filter.fps.priority } else { if (bestFormat.maxFps >= filter.fps.target) leftPoints += filter.fps.priority if (format.maxFps >= filter.fps.target) rightPoints += filter.fps.priority } } // Find closest ISO if (filter.iso != null) { if (filter.iso.target === 'max') { if (bestFormat.maxISO > format.maxISO) leftPoints += filter.iso.priority if (format.maxISO > bestFormat.maxISO) rightPoints += filter.iso.priority } else if (filter.iso.target === 'min') { if (bestFormat.minISO < format.minISO) leftPoints += filter.iso.priority if (format.minISO < bestFormat.minISO) rightPoints += filter.iso.priority } else { if (filter.iso.target >= bestFormat.minISO && filter.iso.target <= bestFormat.maxISO) leftPoints += filter.iso.priority if (filter.iso.target >= format.minISO && filter.iso.target <= format.maxISO) rightPoints += filter.iso.priority } } // Find video stabilization mode if (filter.videoStabilizationMode != null) { if (bestFormat.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) leftPoints += filter.videoStabilizationMode.priority if (format.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) rightPoints += filter.videoStabilizationMode.priority } // Find Photo HDR formats if (filter.photoHdr != null) { if (bestFormat.supportsPhotoHdr === filter.photoHdr.target) leftPoints += filter.photoHdr.priority if (format.supportsPhotoHdr === filter.photoHdr.target) rightPoints += filter.photoHdr.priority } // Find Video HDR formats if (filter.videoHdr != null) { if (bestFormat.supportsVideoHdr === filter.videoHdr.target) leftPoints += filter.videoHdr.priority if (format.supportsVideoHdr === filter.videoHdr.target) rightPoints += filter.videoHdr.priority } // Find matching AF system if (filter.autoFocusSystem != null) { if (bestFormat.autoFocusSystem === filter.autoFocusSystem.target) leftPoints += filter.autoFocusSystem.priority if (format.autoFocusSystem === filter.autoFocusSystem.target) rightPoints += filter.autoFocusSystem.priority } if (rightPoints > leftPoints) bestFormat = format } return bestFormat }