import 'dart:typed_data'; import 'package:image/image.dart' as img; /// X-plore 맞추기 (관리자 지시 10/20): 보기 화면의 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치. /// image 패키지 (MIT - 앱이 이미 쓰는 것, 라이선스 화면에 있음) 로 읽는다. 새 의존 없음. 위치는 보기 화면이 누를 때만 보이고, /// 지도 · 인터넷으로 보내지 않는다 (이 파일은 숫자만 읽는다). class PhotoInfo { /// 그림 크기 (픽셀, 모르면 null) final int? width, height; /// 찍은 날 ("2024-05-01 12:30:00", Exif 의 DateTimeOriginal · 없으면 DateTime) final String? taken; /// 기기 ("Canon EOS 5D" - Make · Model) final String? device; /// 위치 (도, 남 · 서는 음수) final double? latitude, longitude; const PhotoInfo({this.width, this.height, this.taken, this.device, this.latitude, this.longitude}); bool get hasLocation => latitude != null && longitude != null; /// Exif 에서 읽은 것이 있는지 (크기는 Exif 가 없어도 읽음) bool get hasExif => taken != null || device != null || hasLocation; /// 위치 글 ("37.500000, 127.000000") String get locationText => hasLocation ? '${latitude!.toStringAsFixed(6)}, ${longitude!.toStringAsFixed(6)}' : ''; } /// 그림 파일의 바이트에서 사진 정보를 읽는다 (Exif 는 JPEG 만 - 다른 형식은 크기만). 읽지 못하는 것은 null PhotoInfo readPhotoInfo(Uint8List bytes) { int? w, h; try { final info = img.findDecoderForData(bytes)?.startDecode(bytes); if (info != null && info.width > 0 && info.height > 0) { w = info.width; h = info.height; } } catch (_) {} String? taken, device; double? lat, lon; try { final e = img.decodeJpgExif(bytes); if (e != null) { String? s(img.IfdValue? v) { final t = v?.toString().replaceAll('\u0000', '').trim(); return t == null || t.isEmpty ? null : t; } final when = s(e.exifIfd['DateTimeOriginal']) ?? s(e.imageIfd['DateTime']); // "2024:05:01 12:30:00" → "2024-05-01 12:30:00" taken = when?.replaceFirstMapped(RegExp(r'^(\d{4}):(\d{2}):(\d{2})'), (m) => '${m[1]}-${m[2]}-${m[3]}'); final make = s(e.imageIfd['Make']), model = s(e.imageIfd['Model']); device = model == null ? make : (make == null || model.toLowerCase().startsWith(make.toLowerCase()) ? model : '$make $model'); // GPS: 1 위도 N/S · 2 위도 (도 · 분 · 초) · 3 경도 E/W · 4 경도 lat = _coord(e.gpsIfd[2], s(e.gpsIfd[1])); lon = _coord(e.gpsIfd[4], s(e.gpsIfd[3])); } } catch (_) {} return PhotoInfo(width: w, height: h, taken: taken, device: device, latitude: lat, longitude: lon); } double? _coord(img.IfdValue? v, String? ref) { if (v == null || v.length < 3) return null; final d = v.toDouble(0) + v.toDouble(1) / 60 + v.toDouble(2) / 3600; if (d.isNaN || d.isInfinite) return null; return ref == 'S' || ref == 'W' ? -d : d; }