docs: New V3 docs for new API (#1842)
* docs: New V3 docs for new API * fix: Prefer Wide-Angle unless explicitly opted-out * docs: Update DEVICES * Finish Devices docs * Switch links * Revert "Switch links" This reverts commit 06f196ae0e67787cbd5768e125be6d0a3cb5bbc9. * docs: New LIFECYCLE * docs: New CAPTURING docs * Update Worklets links * docs: Update TROUBLESHOOTING and ZOOMING * fix: Update `getAvailableCameraDevices()` usages * docs: Update FORMATS * Update Errors.kt * docs: Fix broken links * docs: Update references to old hooks * docs: Create Frame Processor Tips * docs: Auto-dark mode * fix: Fix FPS filter * feat: Add `'max'` flag to format filter * fix: Use loop * fix: Fix bug in `getCameraFormat` * fix: Find best aspect ratio as well * fix: Switch between formats on FPS change * Update FRAME_PROCESSOR_PLUGIN_LIST.mdx * Add FPS graph explanation * feat: Support HDR filter * docs: Add HDR docs * docs: Add Video Stabilization * docs: Update Skia docs * Skia links * Add Skia labels * Update SKIA_FRAME_PROCESSORS.mdx * docs: Add Performance * Update some wording * Update headers / and zoom * Add examples for devices * fix highlights * fix: Expose `Frame` * docs: Update FP docs * Update links * Update FRAME_PROCESSOR_CREATE_PLUGIN_IOS.mdx
This commit is contained in:
@@ -24,39 +24,57 @@ export interface DeviceFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best matching Camera device that satisfies your requirements using a sorting filter.
|
||||
* @param devices All available Camera Devices this function will use for filtering. To get devices, use `Camera.getAvailableCameraDevices()`.
|
||||
* @param filter The filter you want to use. The device that matches your filter the closest will be returned.
|
||||
* @returns The device that matches your filter the closest.
|
||||
* Get the best matching Camera device that best satisfies your requirements using a sorting filter.
|
||||
* @param position The position of the Camera device relative to the phone.
|
||||
* @param filter The filter you want to use. The Camera device that matches your filter the closest will be returned
|
||||
* @returns The Camera device that matches your filter the closest, or `undefined` if no such Camera Device exists on the given {@linkcode position}.
|
||||
* @example
|
||||
* ```ts
|
||||
* const devices = Camera.getAvailableCameraDevices()
|
||||
* const device = getCameraDevice(devices, 'back', {
|
||||
* physicalDevices: ['wide-angle-camera']
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function getCameraDevice(devices: CameraDevice[], position: CameraPosition, filter: DeviceFilter = {}): CameraDevice {
|
||||
const explicitlyWantsNonWideAngle = filter.physicalDevices != null && !filter.physicalDevices.includes('wide-angle-camera');
|
||||
|
||||
const filtered = devices.filter((d) => d.position === position);
|
||||
const sortedDevices = filtered.sort((left, right) => {
|
||||
|
||||
let bestDevice = filtered[0];
|
||||
if (bestDevice == null) throw new CameraRuntimeError('device/invalid-device', 'No Camera Device could be found!');
|
||||
|
||||
// Compare each device using a point scoring system
|
||||
for (const device of devices) {
|
||||
let leftPoints = 0;
|
||||
let rightPoints = 0;
|
||||
|
||||
// prefer higher hardware-level
|
||||
if (left.hardwareLevel === 'full') leftPoints += 4;
|
||||
if (right.hardwareLevel === 'full') rightPoints += 4;
|
||||
if (bestDevice.hardwareLevel === 'full') leftPoints += 4;
|
||||
if (device.hardwareLevel === 'full') rightPoints += 4;
|
||||
|
||||
if (!explicitlyWantsNonWideAngle) {
|
||||
// prefer wide-angle-camera as a default
|
||||
if (bestDevice.physicalDevices.includes('wide-angle-camera')) leftPoints += 1;
|
||||
if (device.physicalDevices.includes('wide-angle-camera')) rightPoints += 1;
|
||||
}
|
||||
|
||||
// compare devices. two possible scenarios:
|
||||
// 1. user wants all cameras ([ultra-wide, wide, tele]) to zoom. prefer those devices that have all 3 cameras.
|
||||
// 2. user wants only one ([wide]) for faster performance. prefer those devices that only have one camera, if they have more, we rank them lower.
|
||||
if (filter.physicalDevices != null) {
|
||||
for (const device of left.physicalDevices) {
|
||||
if (filter.physicalDevices.includes(device)) leftPoints += 1;
|
||||
for (const d of bestDevice.physicalDevices) {
|
||||
if (filter.physicalDevices.includes(d)) leftPoints += 1;
|
||||
else leftPoints -= 1;
|
||||
}
|
||||
for (const device of right.physicalDevices) {
|
||||
if (filter.physicalDevices.includes(device)) rightPoints += 1;
|
||||
for (const d of device.physicalDevices) {
|
||||
if (filter.physicalDevices.includes(d)) rightPoints += 1;
|
||||
else rightPoints -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return leftPoints - rightPoints;
|
||||
});
|
||||
if (rightPoints > leftPoints) bestDevice = device;
|
||||
}
|
||||
|
||||
const device = sortedDevices[0];
|
||||
if (device == null) throw new CameraRuntimeError('device/invalid-device', 'No Camera Device could be found!');
|
||||
return device;
|
||||
return bestDevice;
|
||||
}
|
||||
|
@@ -12,12 +12,12 @@ 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;
|
||||
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;
|
||||
photoResolution?: Size | 'max';
|
||||
/**
|
||||
* The target aspect ratio of the video (and preview) output, expressed as a factor: `width / height`.
|
||||
*
|
||||
@@ -58,6 +58,14 @@ export interface FormatFilter {
|
||||
* If no format supports the target pixel format, the best other matching format will be used.
|
||||
*/
|
||||
pixelFormat?: PixelFormat;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
type FilterWithPriority<T> = {
|
||||
@@ -84,96 +92,121 @@ function filtersToFilterMap(filters: FormatFilter[]): FilterMap {
|
||||
|
||||
/**
|
||||
* 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 filter you want to use. The format that matches your filter the closest will be returned. The filter is ranked by priority, descending.
|
||||
* @param filter The filter 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);
|
||||
|
||||
// Sort list because we will pick first element
|
||||
// TODO: Use reduce instead of sort?
|
||||
const copy = [...device.formats];
|
||||
const sortedFormats = copy.sort((left, right) => {
|
||||
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;
|
||||
|
||||
const leftVideoResolution = left.videoWidth * left.videoHeight;
|
||||
const rightVideoResolution = right.videoWidth * right.videoHeight;
|
||||
const leftVideoResolution = bestFormat.videoWidth * bestFormat.videoHeight;
|
||||
const rightVideoResolution = format.videoWidth * format.videoHeight;
|
||||
if (filter.videoResolution != null) {
|
||||
// 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;
|
||||
else if (rightDiff < leftDiff) rightPoints += filter.videoResolution.priority;
|
||||
} else {
|
||||
// No filter is set, so just prefer higher resolutions
|
||||
if (leftVideoResolution > rightVideoResolution) leftPoints++;
|
||||
else if (rightVideoResolution > leftVideoResolution) rightPoints++;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const leftPhotoResolution = left.photoWidth * left.photoHeight;
|
||||
const rightPhotoResolution = right.photoWidth * right.photoHeight;
|
||||
const leftPhotoResolution = bestFormat.photoWidth * bestFormat.photoHeight;
|
||||
const rightPhotoResolution = format.photoWidth * format.photoHeight;
|
||||
if (filter.photoResolution != null) {
|
||||
// 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;
|
||||
else if (rightDiff < leftDiff) rightPoints += filter.photoResolution.priority;
|
||||
} else {
|
||||
// No filter is set, so just prefer higher resolutions
|
||||
if (leftPhotoResolution > rightPhotoResolution) leftPoints++;
|
||||
else if (rightPhotoResolution > leftPhotoResolution) rightPoints++;
|
||||
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 = left.videoWidth / right.videoHeight;
|
||||
const rightAspect = right.videoWidth / right.videoHeight;
|
||||
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;
|
||||
else if (rightDiff < leftDiff) rightPoints += filter.videoAspectRatio.priority;
|
||||
if (rightDiff < leftDiff) rightPoints += filter.videoAspectRatio.priority;
|
||||
}
|
||||
|
||||
// Find closest aspect ratio (photo)
|
||||
if (filter.photoAspectRatio != null) {
|
||||
const leftAspect = left.photoWidth / right.photoHeight;
|
||||
const rightAspect = right.photoWidth / right.photoHeight;
|
||||
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;
|
||||
else if (rightDiff < leftDiff) rightPoints += filter.photoAspectRatio.priority;
|
||||
if (rightDiff < leftDiff) rightPoints += filter.photoAspectRatio.priority;
|
||||
}
|
||||
|
||||
// Find closest max FPS
|
||||
if (filter.fps != null) {
|
||||
const leftDiff = Math.abs(left.maxFps - filter.fps.target);
|
||||
const rightDiff = Math.abs(right.maxFps - filter.fps.target);
|
||||
if (leftDiff < rightDiff) leftPoints += filter.fps.priority;
|
||||
else if (rightDiff < leftDiff) rightPoints += filter.fps.priority;
|
||||
if (bestFormat.maxFps >= filter.fps.target) leftPoints += filter.fps.priority;
|
||||
if (format.maxFps >= filter.fps.target) rightPoints += filter.fps.priority;
|
||||
}
|
||||
|
||||
// Find video stabilization mode
|
||||
if (filter.videoStabilizationMode != null) {
|
||||
if (left.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) leftPoints++;
|
||||
if (right.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) rightPoints++;
|
||||
if (bestFormat.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) leftPoints++;
|
||||
if (format.videoStabilizationModes.includes(filter.videoStabilizationMode.target)) rightPoints++;
|
||||
}
|
||||
|
||||
// Find pixel format
|
||||
if (filter.pixelFormat != null) {
|
||||
if (left.pixelFormats.includes(filter.pixelFormat.target)) leftPoints++;
|
||||
if (right.pixelFormats.includes(filter.pixelFormat.target)) rightPoints++;
|
||||
if (bestFormat.pixelFormats.includes(filter.pixelFormat.target)) leftPoints++;
|
||||
if (format.pixelFormats.includes(filter.pixelFormat.target)) rightPoints++;
|
||||
}
|
||||
|
||||
return rightPoints - leftPoints;
|
||||
});
|
||||
// Find Photo HDR formats
|
||||
if (filter.photoHDR != null) {
|
||||
if (bestFormat.supportsPhotoHDR === filter.photoHDR.target) leftPoints++;
|
||||
if (format.supportsPhotoHDR === filter.photoHDR.target) rightPoints++;
|
||||
}
|
||||
|
||||
const format = sortedFormats[0];
|
||||
if (format == null)
|
||||
throw new CameraRuntimeError('device/invalid-device', `The given Camera Device (${device.id}) does not have any formats!`);
|
||||
return format;
|
||||
// Find Video HDR formats
|
||||
if (filter.videoHDR != null) {
|
||||
if (bestFormat.supportsVideoHDR === filter.videoHDR.target) leftPoints++;
|
||||
if (format.supportsVideoHDR === filter.videoHDR.target) rightPoints++;
|
||||
}
|
||||
|
||||
if (rightPoints > leftPoints) bestFormat = format;
|
||||
}
|
||||
|
||||
return bestFormat;
|
||||
}
|
||||
|
Reference in New Issue
Block a user