88 lines
4.3 KiB
Dart
88 lines
4.3 KiB
Dart
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:image/image.dart' as img;
|
||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||
import 'package:jj_mkvmaker/core/photo_info.dart';
|
||
import 'package:jj_mkvmaker/core/reader_sources.dart';
|
||
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||
import 'package:jj_mkvmaker/ui/reader_page.dart';
|
||
import 'package:path/path.dart' as p;
|
||
|
||
/// 도 · 분 · 초 (image 패키지가 Rational 을 내보내지 않아 IfdValueRational 하나씩 만들어 잇는다)
|
||
img.IfdValueRational _dms(int d, int m, int s) =>
|
||
img.IfdValueRational(d, 1)..value.addAll([...img.IfdValueRational(m, 1).value, ...img.IfdValueRational(s, 1).value]);
|
||
|
||
/// Exif 가 든 JPEG (찍은 날 · 기기 · 위치)
|
||
Uint8List _jpeg({bool gps = true, bool south = false}) {
|
||
final im = img.Image(width: 30, height: 20);
|
||
im.exif.imageIfd['Make'] = 'Canon';
|
||
im.exif.imageIfd['Model'] = 'Canon EOS 5D';
|
||
im.exif.exifIfd['DateTimeOriginal'] = '2024:05:01 12:30:00';
|
||
if (gps) {
|
||
im.exif.gpsIfd[1] = img.IfdValueAscii(south ? 'S' : 'N');
|
||
im.exif.gpsIfd[2] = _dms(37, 30, 0);
|
||
im.exif.gpsIfd[3] = img.IfdValueAscii('E');
|
||
im.exif.gpsIfd[4] = _dms(127, 0, 36);
|
||
}
|
||
return Uint8List.fromList(img.encodeJpg(im));
|
||
}
|
||
|
||
/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치 (누를 때만, 보내지 않음)
|
||
void main() {
|
||
test('Exif 읽기: 찍은 날 (2024-05-01) · 기기 (Make 가 Model 앞에 있으면 한 번만) · 크기 · 위치 (도 · 분 · 초 → 도, 남은 음수)', () {
|
||
final i = readPhotoInfo(_jpeg());
|
||
expect(i.width, 30);
|
||
expect(i.height, 20);
|
||
expect(i.taken, '2024-05-01 12:30:00');
|
||
expect(i.device, 'Canon EOS 5D');
|
||
expect(i.latitude, closeTo(37.5, 1e-6));
|
||
expect(i.longitude, closeTo(127.01, 1e-6));
|
||
expect(i.locationText, '37.500000, 127.010000');
|
||
expect(i.hasExif, isTrue);
|
||
expect(readPhotoInfo(_jpeg(south: true)).latitude, closeTo(-37.5, 1e-6));
|
||
expect(readPhotoInfo(_jpeg(gps: false)).hasLocation, isFalse);
|
||
});
|
||
|
||
test('Exif 가 없는 그림 (PNG) 은 크기만, 깨진 바이트는 아무것도 없이 (예외 없음)', () {
|
||
final png = readPhotoInfo(Uint8List.fromList(img.encodePng(img.Image(width: 7, height: 9))));
|
||
expect((png.width, png.height), (7, 9));
|
||
expect(png.hasExif, isFalse);
|
||
final bad = readPhotoInfo(Uint8List.fromList([1, 2, 3]));
|
||
expect(bad.width, isNull);
|
||
expect(bad.hasExif, isFalse);
|
||
});
|
||
|
||
testWidgets('보기 화면 [사진 정보 (Exif)]: 찍은 날 · 기기 · 크기, 위치는 [위치 보기] 를 눌러야 보임 (링크 없음)', (tester) async {
|
||
tester.view.physicalSize = const Size(1400, 1200);
|
||
tester.view.devicePixelRatio = 1;
|
||
addTearDown(tester.view.reset);
|
||
final tmp = Directory.systemTemp.createTempSync('jj_photoinfo_');
|
||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||
final f = File(p.join(tmp.path, 'trip.jpg'))..writeAsBytesSync(_jpeg());
|
||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||
await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource([f.path], tempDir: tmp.path, title: 'x'))));
|
||
await tester.pump();
|
||
await tester.tap(find.byTooltip('사진 정보 (Exif)'));
|
||
for (var k = 0; k < 10; k++) {
|
||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||
await tester.pump();
|
||
}
|
||
expect(find.text('2024-05-01 12:30:00'), findsOneWidget);
|
||
expect(find.text('Canon EOS 5D'), findsOneWidget);
|
||
expect(find.textContaining('30 × 20 픽셀'), findsOneWidget);
|
||
expect(find.textContaining('37.5'), findsNothing, reason: '위치는 누를 때만');
|
||
await tester.tap(find.text('위치 보기'));
|
||
await tester.pump();
|
||
expect(find.text('37.500000, 127.010000'), findsOneWidget);
|
||
expect(find.textContaining('http'), findsNothing, reason: '지도 · 인터넷 링크 없음');
|
||
await tester.tap(find.text('닫기'));
|
||
await tester.pumpAndSettle();
|
||
await tester.pumpWidget(const SizedBox());
|
||
});
|
||
}
|