panorama: loosened identification criteria, handle missing parameter

This commit is contained in:
Thibault Deckers 2021-01-27 11:03:55 +09:00
parent 79b6276846
commit 79aefc3aa5
3 changed files with 33 additions and 15 deletions

View file

@ -594,14 +594,16 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input)
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java) val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
try { try {
fun getProp(propName: String): Int? = xmpDirs.map { it.xmpMeta.getPropertyInteger(XMP.GPANO_SCHEMA_NS, propName) }.firstOrNull { it != null } fun getIntProp(propName: String): Int? = xmpDirs.map { it.xmpMeta.getPropertyInteger(XMP.GPANO_SCHEMA_NS, propName) }.firstOrNull { it != null }
fun getStringProp(propName: String): String? = xmpDirs.map { it.xmpMeta.getPropertyString(XMP.GPANO_SCHEMA_NS, propName) }.firstOrNull { it != null }
val fields: FieldMap = hashMapOf( val fields: FieldMap = hashMapOf(
"croppedAreaLeft" to getProp(XMP.GPANO_CROPPED_AREA_LEFT_PROP_NAME), "croppedAreaLeft" to getIntProp(XMP.GPANO_CROPPED_AREA_LEFT_PROP_NAME),
"croppedAreaTop" to getProp(XMP.GPANO_CROPPED_AREA_TOP_PROP_NAME), "croppedAreaTop" to getIntProp(XMP.GPANO_CROPPED_AREA_TOP_PROP_NAME),
"croppedAreaWidth" to getProp(XMP.GPANO_CROPPED_AREA_WIDTH_PROP_NAME), "croppedAreaWidth" to getIntProp(XMP.GPANO_CROPPED_AREA_WIDTH_PROP_NAME),
"croppedAreaHeight" to getProp(XMP.GPANO_CROPPED_AREA_HEIGHT_PROP_NAME), "croppedAreaHeight" to getIntProp(XMP.GPANO_CROPPED_AREA_HEIGHT_PROP_NAME),
"fullPanoWidth" to getProp(XMP.GPANO_FULL_PANO_WIDTH_PROP_NAME), "fullPanoWidth" to getIntProp(XMP.GPANO_FULL_PANO_WIDTH_PROP_NAME),
"fullPanoHeight" to getProp(XMP.GPANO_FULL_PANO_HEIGHT_PROP_NAME), "fullPanoHeight" to getIntProp(XMP.GPANO_FULL_PANO_HEIGHT_PROP_NAME),
"projectionType" to (getStringProp(XMP.GPANO_PROJECTION_TYPE_PROP_NAME) ?: XMP.GPANO_PROJECTION_TYPE_DEFAULT),
) )
result.success(fields) result.success(fields)
return return

View file

@ -54,18 +54,19 @@ object XMP {
const val GPANO_CROPPED_AREA_TOP_PROP_NAME = "GPano:CroppedAreaTopPixels" const val GPANO_CROPPED_AREA_TOP_PROP_NAME = "GPano:CroppedAreaTopPixels"
const val GPANO_FULL_PANO_HEIGHT_PROP_NAME = "GPano:FullPanoHeightPixels" const val GPANO_FULL_PANO_HEIGHT_PROP_NAME = "GPano:FullPanoHeightPixels"
const val GPANO_FULL_PANO_WIDTH_PROP_NAME = "GPano:FullPanoWidthPixels" const val GPANO_FULL_PANO_WIDTH_PROP_NAME = "GPano:FullPanoWidthPixels"
private const val GPANO_PROJECTION_TYPE_PROP_NAME = "GPano:ProjectionType" const val GPANO_PROJECTION_TYPE_PROP_NAME = "GPano:ProjectionType"
const val GPANO_PROJECTION_TYPE_DEFAULT = "equirectangular"
private const val PMTM_IS_PANO360 = "pmtm:IsPano360" private const val PMTM_IS_PANO360 = "pmtm:IsPano360"
// `GPano:ProjectionType` is required by spec but it is sometimes missing, assuming default
// `GPano:FullPanoHeightPixels` is required by spec but it is sometimes missing (e.g. Samsung Camera app panorama mode)
private val gpanoRequiredProps = listOf( private val gpanoRequiredProps = listOf(
GPANO_CROPPED_AREA_HEIGHT_PROP_NAME, GPANO_CROPPED_AREA_HEIGHT_PROP_NAME,
GPANO_CROPPED_AREA_WIDTH_PROP_NAME, GPANO_CROPPED_AREA_WIDTH_PROP_NAME,
GPANO_CROPPED_AREA_LEFT_PROP_NAME, GPANO_CROPPED_AREA_LEFT_PROP_NAME,
GPANO_CROPPED_AREA_TOP_PROP_NAME, GPANO_CROPPED_AREA_TOP_PROP_NAME,
GPANO_FULL_PANO_HEIGHT_PROP_NAME,
GPANO_FULL_PANO_WIDTH_PROP_NAME, GPANO_FULL_PANO_WIDTH_PROP_NAME,
GPANO_PROJECTION_TYPE_PROP_NAME,
) )
// extensions // extensions

View file

@ -4,24 +4,38 @@ import 'package:flutter/widgets.dart';
class PanoramaInfo { class PanoramaInfo {
final Rect croppedAreaRect; final Rect croppedAreaRect;
final Size fullPanoSize; final Size fullPanoSize;
final String projectionType;
PanoramaInfo({ PanoramaInfo({
this.croppedAreaRect, this.croppedAreaRect,
this.fullPanoSize, this.fullPanoSize,
this.projectionType,
}); });
factory PanoramaInfo.fromMap(Map map) { factory PanoramaInfo.fromMap(Map map) {
final cLeft = map['croppedAreaLeft'] as int; var cLeft = map['croppedAreaLeft'] as int;
final cTop = map['croppedAreaTop'] as int; var cTop = map['croppedAreaTop'] as int;
final cWidth = map['croppedAreaWidth'] as int; final cWidth = map['croppedAreaWidth'] as int;
final cHeight = map['croppedAreaHeight'] as int; final cHeight = map['croppedAreaHeight'] as int;
var fWidth = map['fullPanoWidth'] as int;
var fHeight = map['fullPanoHeight'] as int;
final projectionType = map['projectionType'] as String;
// handle missing `fullPanoHeight` (e.g. Samsung camera app panorama mode)
if (fHeight == null && cWidth != null && cHeight != null) {
// assume the cropped area is actually covering 360 degrees horizontally
// even when `croppedAreaLeft` is non zero
fWidth = cWidth;
fHeight = (fWidth / 2).round();
cTop = ((fHeight - cHeight) / 2).round();
cLeft = 0;
}
Rect croppedAreaRect; Rect croppedAreaRect;
if (cLeft != null && cTop != null && cWidth != null && cHeight != null) { if (cLeft != null && cTop != null && cWidth != null && cHeight != null) {
croppedAreaRect = Rect.fromLTWH(cLeft.toDouble(), cTop.toDouble(), cWidth.toDouble(), cHeight.toDouble()); croppedAreaRect = Rect.fromLTWH(cLeft.toDouble(), cTop.toDouble(), cWidth.toDouble(), cHeight.toDouble());
} }
final fWidth = map['fullPanoWidth'] as int;
final fHeight = map['fullPanoHeight'] as int;
Size fullPanoSize; Size fullPanoSize;
if (fWidth != null && fHeight != null) { if (fWidth != null && fHeight != null) {
fullPanoSize = Size(fWidth.toDouble(), fHeight.toDouble()); fullPanoSize = Size(fWidth.toDouble(), fHeight.toDouble());
@ -30,11 +44,12 @@ class PanoramaInfo {
return PanoramaInfo( return PanoramaInfo(
croppedAreaRect: croppedAreaRect, croppedAreaRect: croppedAreaRect,
fullPanoSize: fullPanoSize, fullPanoSize: fullPanoSize,
projectionType: projectionType,
); );
} }
bool get hasCroppedArea => croppedAreaRect != null && fullPanoSize != null; bool get hasCroppedArea => croppedAreaRect != null && fullPanoSize != null;
@override @override
String toString() => '$runtimeType#${shortHash(this)}{croppedAreaRect=$croppedAreaRect, fullPanoSize=$fullPanoSize}'; String toString() => '$runtimeType#${shortHash(this)}{croppedAreaRect=$croppedAreaRect, fullPanoSize=$fullPanoSize, projectionType=$projectionType}';
} }