Merge branch 'tmp'

This commit is contained in:
Ilya Laktyushin 2018-05-12 02:29:16 +04:00
commit 2316de38da
21 changed files with 2312 additions and 11 deletions

View File

@ -7,8 +7,20 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
09053D7920A5CCF10029652D /* genann.c in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7120A5CCEF0029652D /* genann.c */; };
09053D7A20A5CCF10029652D /* fast-edge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7220A5CCEF0029652D /* fast-edge.cpp */; };
09053D7B20A5CCF10029652D /* TGPassportOCR.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7320A5CCF00029652D /* TGPassportOCR.h */; settings = {ATTRIBUTES = (Public, ); }; };
09053D7C20A5CCF10029652D /* ocr.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7420A5CCF00029652D /* ocr.h */; };
09053D7D20A5CCF10029652D /* TGPassportOCR.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7520A5CCF00029652D /* TGPassportOCR.mm */; };
09053D7E20A5CCF10029652D /* genann.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7620A5CCF00029652D /* genann.h */; };
09053D7F20A5CCF10029652D /* fast-edge.h in Headers */ = {isa = PBXBuildFile; fileRef = 09053D7720A5CCF00029652D /* fast-edge.h */; };
09053D8020A5CCF10029652D /* ocr.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09053D7820A5CCF00029652D /* ocr.mm */; };
090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */ = {isa = PBXBuildFile; fileRef = 090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */; }; 090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */ = {isa = PBXBuildFile; fileRef = 090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */; };
090671C51F67F71700CCF2F5 /* TGLocationOptionsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */; }; 090671C51F67F71700CCF2F5 /* TGLocationOptionsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */; };
0916FEA720A1EA7B0084A755 /* TGPassportScanView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */; settings = {ATTRIBUTES = (Public, ); }; };
0916FEA820A1EA7B0084A755 /* TGPassportScanView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */; };
0916FEAB20A1EBFA0084A755 /* TGPassportMRZ.m in Sources */ = {isa = PBXBuildFile; fileRef = 0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */; };
0916FEAC20A1EBFA0084A755 /* TGPassportMRZ.h in Headers */ = {isa = PBXBuildFile; fileRef = 0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */; settings = {ATTRIBUTES = (Public, ); }; };
09750F761F2FA816001B9886 /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09750F741F2FA5E8001B9886 /* SSignalKit.framework */; }; 09750F761F2FA816001B9886 /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09750F741F2FA5E8001B9886 /* SSignalKit.framework */; };
09750FB71F30DB0E001B9886 /* TGClipboardGalleryModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 09750FB71F30DB0E001B9886 /* TGClipboardGalleryModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
09750FB81F30DB0E001B9886 /* TGClipboardGalleryModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */; }; 09750FB81F30DB0E001B9886 /* TGClipboardGalleryModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */; };
@ -1179,8 +1191,20 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
09053D7120A5CCEF0029652D /* genann.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = genann.c; sourceTree = "<group>"; };
09053D7220A5CCEF0029652D /* fast-edge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "fast-edge.cpp"; sourceTree = "<group>"; };
09053D7320A5CCF00029652D /* TGPassportOCR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPassportOCR.h; sourceTree = "<group>"; };
09053D7420A5CCF00029652D /* ocr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocr.h; sourceTree = "<group>"; };
09053D7520A5CCF00029652D /* TGPassportOCR.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGPassportOCR.mm; sourceTree = "<group>"; };
09053D7620A5CCF00029652D /* genann.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = genann.h; sourceTree = "<group>"; };
09053D7720A5CCF00029652D /* fast-edge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "fast-edge.h"; sourceTree = "<group>"; };
09053D7820A5CCF00029652D /* ocr.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ocr.mm; sourceTree = "<group>"; };
090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLocationOptionsView.h; sourceTree = "<group>"; }; 090671C21F67F71700CCF2F5 /* TGLocationOptionsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLocationOptionsView.h; sourceTree = "<group>"; };
090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLocationOptionsView.m; sourceTree = "<group>"; }; 090671C31F67F71700CCF2F5 /* TGLocationOptionsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLocationOptionsView.m; sourceTree = "<group>"; };
0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TGPassportScanView.h; sourceTree = "<group>"; };
0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TGPassportScanView.m; sourceTree = "<group>"; };
0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPassportMRZ.m; sourceTree = "<group>"; };
0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPassportMRZ.h; sourceTree = "<group>"; };
09750F741F2FA5E8001B9886 /* SSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegraph-grvwvmixbmcefwboxkzfazvpcrxb/Build/Products/Release Hockeyapp-iphonesimulator/SSignalKit.framework"; sourceTree = "<group>"; }; 09750F741F2FA5E8001B9886 /* SSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegraph-grvwvmixbmcefwboxkzfazvpcrxb/Build/Products/Release Hockeyapp-iphonesimulator/SSignalKit.framework"; sourceTree = "<group>"; };
09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGClipboardGalleryModel.h; sourceTree = "<group>"; }; 09750FAF1F30DB0E001B9886 /* TGClipboardGalleryModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGClipboardGalleryModel.h; sourceTree = "<group>"; };
09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGClipboardGalleryModel.m; sourceTree = "<group>"; }; 09750FB01F30DB0E001B9886 /* TGClipboardGalleryModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGClipboardGalleryModel.m; sourceTree = "<group>"; };
@ -2365,6 +2389,25 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
093134BC20A1EA3F003045EE /* Passport */ = {
isa = PBXGroup;
children = (
09053D7220A5CCEF0029652D /* fast-edge.cpp */,
09053D7720A5CCF00029652D /* fast-edge.h */,
09053D7120A5CCEF0029652D /* genann.c */,
09053D7620A5CCF00029652D /* genann.h */,
09053D7420A5CCF00029652D /* ocr.h */,
09053D7820A5CCF00029652D /* ocr.mm */,
09053D7320A5CCF00029652D /* TGPassportOCR.h */,
09053D7520A5CCF00029652D /* TGPassportOCR.mm */,
0916FEA520A1EA7B0084A755 /* TGPassportScanView.h */,
0916FEA620A1EA7B0084A755 /* TGPassportScanView.m */,
0916FEAA20A1EBFA0084A755 /* TGPassportMRZ.h */,
0916FEA920A1EBF90084A755 /* TGPassportMRZ.m */,
);
name = Passport;
sourceTree = "<group>";
};
09750FAE1F30DAE1001B9886 /* Clipboard Menu */ = { 09750FAE1F30DAE1001B9886 /* Clipboard Menu */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2406,6 +2449,7 @@
D01777291F1F8F100044446D /* LegacyComponents */ = { D01777291F1F8F100044446D /* LegacyComponents */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
093134BC20A1EA3F003045EE /* Passport */,
D0EB409E1F2FC0AA00838FE6 /* Resources */, D0EB409E1F2FC0AA00838FE6 /* Resources */,
D017776F1F1F91B00044446D /* Utils */, D017776F1F1F91B00044446D /* Utils */,
D01779B01F2139720044446D /* POP */, D01779B01F2139720044446D /* POP */,
@ -3908,6 +3952,7 @@
D01778191F1F961D0044446D /* TGMessageEntityHashtag.h in Headers */, D01778191F1F961D0044446D /* TGMessageEntityHashtag.h in Headers */,
D07BCA341F2A9B0400ED97AA /* TGModernGalleryEditableItemView.h in Headers */, D07BCA341F2A9B0400ED97AA /* TGModernGalleryEditableItemView.h in Headers */,
D0177A911F221BB10044446D /* TGModernGalleryTransitionView.h in Headers */, D0177A911F221BB10044446D /* TGModernGalleryTransitionView.h in Headers */,
0916FEAC20A1EBFA0084A755 /* TGPassportMRZ.h in Headers */,
D01778E51F20CAE60044446D /* TGNavigationController.h in Headers */, D01778E51F20CAE60044446D /* TGNavigationController.h in Headers */,
D01777721F1F92420044446D /* TGPhoneUtils.h in Headers */, D01777721F1F92420044446D /* TGPhoneUtils.h in Headers */,
D07BC7721F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.h in Headers */, D07BC7721F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.h in Headers */,
@ -3949,11 +3994,13 @@
D01779261F20FE480044446D /* TGObserverProxy.h in Headers */, D01779261F20FE480044446D /* TGObserverProxy.h in Headers */,
D07BCADF1F2B4F5E00ED97AA /* TGLegacyCameraController.h in Headers */, D07BCADF1F2B4F5E00ED97AA /* TGLegacyCameraController.h in Headers */,
D017782F1F1F961D0044446D /* TGReplyMarkupAttachment.h in Headers */, D017782F1F1F961D0044446D /* TGReplyMarkupAttachment.h in Headers */,
0916FEA720A1EA7B0084A755 /* TGPassportScanView.h in Headers */,
D07BCB211F2B646A00ED97AA /* TGPasscodeBackground.h in Headers */, D07BCB211F2B646A00ED97AA /* TGPasscodeBackground.h in Headers */,
D01778371F1F961D0044446D /* TGAudioMediaAttachment.h in Headers */, D01778371F1F961D0044446D /* TGAudioMediaAttachment.h in Headers */,
D07BC6F51F2A19A700ED97AA /* TGCameraSegmentsView.h in Headers */, D07BC6F51F2A19A700ED97AA /* TGCameraSegmentsView.h in Headers */,
D04269051F586A070037ECE8 /* TGVideoMessageScrubber.h in Headers */, D04269051F586A070037ECE8 /* TGVideoMessageScrubber.h in Headers */,
D07BC87F1F2A365000ED97AA /* TGProgressWindow.h in Headers */, D07BC87F1F2A365000ED97AA /* TGProgressWindow.h in Headers */,
09053D7F20A5CCF10029652D /* fast-edge.h in Headers */,
D07BC8001F2A2C0B00ED97AA /* PGGrainTool.h in Headers */, D07BC8001F2A2C0B00ED97AA /* PGGrainTool.h in Headers */,
D01779EA1F2139980044446D /* POPAnimationPrivate.h in Headers */, D01779EA1F2139980044446D /* POPAnimationPrivate.h in Headers */,
D017775C1F1F8FE60044446D /* PSKeyValueStore.h in Headers */, D017775C1F1F8FE60044446D /* PSKeyValueStore.h in Headers */,
@ -4011,6 +4058,7 @@
090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */, 090671C41F67F71700CCF2F5 /* TGLocationOptionsView.h in Headers */,
D07BCAAE1F2B45DA00ED97AA /* TGFileUtils.h in Headers */, D07BCAAE1F2B45DA00ED97AA /* TGFileUtils.h in Headers */,
D0177B1E1F2641B10044446D /* PGCameraMovieWriter.h in Headers */, D0177B1E1F2641B10044446D /* PGCameraMovieWriter.h in Headers */,
09053D7B20A5CCF10029652D /* TGPassportOCR.h in Headers */,
D01779641F2103910044446D /* TGPaintUtils.h in Headers */, D01779641F2103910044446D /* TGPaintUtils.h in Headers */,
D07BCB741F2B6A5600ED97AA /* TGEmbedYoutubePlayerView.h in Headers */, D07BCB741F2B6A5600ED97AA /* TGEmbedYoutubePlayerView.h in Headers */,
D07BC8C51F2A37EC00ED97AA /* TGPhotoPaintEntityView.h in Headers */, D07BC8C51F2A37EC00ED97AA /* TGPhotoPaintEntityView.h in Headers */,
@ -4050,6 +4098,7 @@
D0177A071F2139980044446D /* POPSpringAnimation.h in Headers */, D0177A071F2139980044446D /* POPSpringAnimation.h in Headers */,
D07BC9951F2A481C00ED97AA /* TGStickerKeyboardTabSettingsCell.h in Headers */, D07BC9951F2A481C00ED97AA /* TGStickerKeyboardTabSettingsCell.h in Headers */,
D0177A341F21F1980044446D /* UIImage+TGMediaEditableItem.h in Headers */, D0177A341F21F1980044446D /* UIImage+TGMediaEditableItem.h in Headers */,
09053D7E20A5CCF10029652D /* genann.h in Headers */,
D07BC94E1F2A3EA900ED97AA /* TGHashtagPanelCell.h in Headers */, D07BC94E1F2A3EA900ED97AA /* TGHashtagPanelCell.h in Headers */,
D07BCBAF1F2B6F6300ED97AA /* CBCoubAudioSource.h in Headers */, D07BCBAF1F2B6F6300ED97AA /* CBCoubAudioSource.h in Headers */,
D07BC7841F2A2B3700ED97AA /* TGPhotoEditorSliderView.h in Headers */, D07BC7841F2A2B3700ED97AA /* TGPhotoEditorSliderView.h in Headers */,
@ -4117,6 +4166,7 @@
D01779FE1F2139980044446D /* POPGeometry.h in Headers */, D01779FE1F2139980044446D /* POPGeometry.h in Headers */,
D07BCA981F2B443700ED97AA /* TGMediaAssetsPickerController.h in Headers */, D07BCA981F2B443700ED97AA /* TGMediaAssetsPickerController.h in Headers */,
D07BCBF51F2B72DC00ED97AA /* STKLocalFileDataSource.h in Headers */, D07BCBF51F2B72DC00ED97AA /* STKLocalFileDataSource.h in Headers */,
09053D7C20A5CCF10029652D /* ocr.h in Headers */,
D07BC9F11F2A9A2B00ED97AA /* TGMediaPickerCell.h in Headers */, D07BC9F11F2A9A2B00ED97AA /* TGMediaPickerCell.h in Headers */,
D07BC8371F2A2D0C00ED97AA /* TGSecretTimerPickerItemView.h in Headers */, D07BC8371F2A2D0C00ED97AA /* TGSecretTimerPickerItemView.h in Headers */,
D07BC8D31F2A37EC00ED97AA /* TGPhotoPaintTextEntity.h in Headers */, D07BC8D31F2A37EC00ED97AA /* TGPhotoPaintTextEntity.h in Headers */,
@ -4690,6 +4740,7 @@
D07BC9BB1F2A705D00ED97AA /* TGPhotoFilterCell.m in Sources */, D07BC9BB1F2A705D00ED97AA /* TGPhotoFilterCell.m in Sources */,
D0177A571F21F7F40044446D /* TGDoubleTapGestureRecognizer.m in Sources */, D0177A571F21F7F40044446D /* TGDoubleTapGestureRecognizer.m in Sources */,
D07BC7FF1F2A2C0B00ED97AA /* PGFadeTool.m in Sources */, D07BC7FF1F2A2C0B00ED97AA /* PGFadeTool.m in Sources */,
0916FEAB20A1EBFA0084A755 /* TGPassportMRZ.m in Sources */,
D01778C31F200AF70044446D /* TGAnimationBlockDelegate.m in Sources */, D01778C31F200AF70044446D /* TGAnimationBlockDelegate.m in Sources */,
D07BC85F1F2A2DBD00ED97AA /* TGMenuSheetTitleItemView.m in Sources */, D07BC85F1F2A2DBD00ED97AA /* TGMenuSheetTitleItemView.m in Sources */,
D07BC6EC1F2A19A700ED97AA /* TGCameraFlashActiveView.m in Sources */, D07BC6EC1F2A19A700ED97AA /* TGCameraFlashActiveView.m in Sources */,
@ -4821,6 +4872,7 @@
D07BC85D1F2A2DBD00ED97AA /* TGMenuSheetItemView.m in Sources */, D07BC85D1F2A2DBD00ED97AA /* TGMenuSheetItemView.m in Sources */,
D07BC90E1F2A380D00ED97AA /* TGPainting.m in Sources */, D07BC90E1F2A380D00ED97AA /* TGPainting.m in Sources */,
D0177A441F21F62A0044446D /* TGMediaVideoConverter.m in Sources */, D0177A441F21F62A0044446D /* TGMediaVideoConverter.m in Sources */,
09053D8020A5CCF10029652D /* ocr.mm in Sources */,
D01779A51F210A120044446D /* TGMediaAssetLegacyImageSignals.m in Sources */, D01779A51F210A120044446D /* TGMediaAssetLegacyImageSignals.m in Sources */,
D017777B1F1F927A0044446D /* NSObject+TGLock.m in Sources */, D017777B1F1F927A0044446D /* NSObject+TGLock.m in Sources */,
D01778281F1F961D0044446D /* TGMessageEntitiesAttachment.m in Sources */, D01778281F1F961D0044446D /* TGMessageEntitiesAttachment.m in Sources */,
@ -4836,6 +4888,7 @@
D07BC7181F2A29B700ED97AA /* TGPhotoEditorController.m in Sources */, D07BC7181F2A29B700ED97AA /* TGPhotoEditorController.m in Sources */,
D07BC8251F2A2C0B00ED97AA /* PGSharpenTool.m in Sources */, D07BC8251F2A2C0B00ED97AA /* PGSharpenTool.m in Sources */,
D0177AF41F23DF6D0044446D /* TGImageManagerTask.m in Sources */, D0177AF41F23DF6D0044446D /* TGImageManagerTask.m in Sources */,
09053D7920A5CCF10029652D /* genann.c in Sources */,
D017783F1F1F961D0044446D /* TGDocumentAttributeImageSize.m in Sources */, D017783F1F1F961D0044446D /* TGDocumentAttributeImageSize.m in Sources */,
D01778E61F20CAE60044446D /* TGNavigationController.m in Sources */, D01778E61F20CAE60044446D /* TGNavigationController.m in Sources */,
D07BC8C61F2A37EC00ED97AA /* TGPhotoPaintEntityView.m in Sources */, D07BC8C61F2A37EC00ED97AA /* TGPhotoPaintEntityView.m in Sources */,
@ -4922,6 +4975,7 @@
D07BCB361F2B65F100ED97AA /* TGBuiltinWallpaperInfo.m in Sources */, D07BCB361F2B65F100ED97AA /* TGBuiltinWallpaperInfo.m in Sources */,
D07BC90A1F2A380D00ED97AA /* TGPaintFaceDebugView.m in Sources */, D07BC90A1F2A380D00ED97AA /* TGPaintFaceDebugView.m in Sources */,
D017785A1F1F961D0044446D /* TGImageMediaAttachment.m in Sources */, D017785A1F1F961D0044446D /* TGImageMediaAttachment.m in Sources */,
09053D7D20A5CCF10029652D /* TGPassportOCR.mm in Sources */,
D017784C1F1F961D0044446D /* TGUnsupportedMediaAttachment.m in Sources */, D017784C1F1F961D0044446D /* TGUnsupportedMediaAttachment.m in Sources */,
D01779151F20F4500044446D /* TGStaticBackdropAreaData.m in Sources */, D01779151F20F4500044446D /* TGStaticBackdropAreaData.m in Sources */,
D07BC91C1F2A380D00ED97AA /* TGPaintRender.m in Sources */, D07BC91C1F2A380D00ED97AA /* TGPaintRender.m in Sources */,
@ -5031,6 +5085,7 @@
D01778AA1F1FD0900044446D /* TGImageUtils.mm in Sources */, D01778AA1F1FD0900044446D /* TGImageUtils.mm in Sources */,
D07BCADA1F2B4F2800ED97AA /* TGOverlayFormsheetController.m in Sources */, D07BCADA1F2B4F2800ED97AA /* TGOverlayFormsheetController.m in Sources */,
D07BC8571F2A2DBD00ED97AA /* TGMenuSheetButtonItemView.m in Sources */, D07BC8571F2A2DBD00ED97AA /* TGMenuSheetButtonItemView.m in Sources */,
0916FEA820A1EA7B0084A755 /* TGPassportScanView.m in Sources */,
D07BCA161F2A9A2B00ED97AA /* TGMediaPickerPhotoStripCell.m in Sources */, D07BCA161F2A9A2B00ED97AA /* TGMediaPickerPhotoStripCell.m in Sources */,
D07BCBAD1F2B6F6300ED97AA /* CBConstance.m in Sources */, D07BCBAD1F2B6F6300ED97AA /* CBConstance.m in Sources */,
D017780A1F1F961D0044446D /* TGGameMediaAttachment.m in Sources */, D017780A1F1F961D0044446D /* TGGameMediaAttachment.m in Sources */,
@ -5077,6 +5132,7 @@
D07BCBD01F2B6F6300ED97AA /* NSDictionary+CBExtensions.m in Sources */, D07BCBD01F2B6F6300ED97AA /* NSDictionary+CBExtensions.m in Sources */,
D07BC7731F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.m in Sources */, D07BC7731F2A2B3700ED97AA /* TGPhotoEditorCurvesHistogramView.m in Sources */,
D07BCB3E1F2B65F100ED97AA /* TGWallpaperInfo.m in Sources */, D07BCB3E1F2B65F100ED97AA /* TGWallpaperInfo.m in Sources */,
09053D7A20A5CCF10029652D /* fast-edge.cpp in Sources */,
D07BCA1C1F2A9A2B00ED97AA /* TGMediaPickerSelectionGestureRecognizer.m in Sources */, D07BCA1C1F2A9A2B00ED97AA /* TGMediaPickerSelectionGestureRecognizer.m in Sources */,
D07BC9141F2A380D00ED97AA /* TGPaintNeonBrush.m in Sources */, D07BC9141F2A380D00ED97AA /* TGPaintNeonBrush.m in Sources */,
D01778161F1F961D0044446D /* TGMessageEntityCode.m in Sources */, D01778161F1F961D0044446D /* TGMessageEntityCode.m in Sources */,

View File

@ -278,6 +278,9 @@ FOUNDATION_EXPORT const unsigned char LegacyComponentsVersionString[];
#import <LegacyComponents/TGAttachmentCameraView.h> #import <LegacyComponents/TGAttachmentCameraView.h>
#import <LegacyComponents/TGMediaAvatarMenuMixin.h> #import <LegacyComponents/TGMediaAvatarMenuMixin.h>
#import <LegacyComponents/TGPassportAttachMenu.h> #import <LegacyComponents/TGPassportAttachMenu.h>
#import <LegacyComponents/TGPassportScanView.h>
#import <LegacyComponents/TGPassportOCR.h>
#import <LegacyComponents/TGPassportMRZ.h>
#import <LegacyComponents/TGPasscodeEntryController.h> #import <LegacyComponents/TGPasscodeEntryController.h>
#import <LegacyComponents/TGEmbedPlayerView.h> #import <LegacyComponents/TGEmbedPlayerView.h>
#import <LegacyComponents/TGWallpaperInfo.h> #import <LegacyComponents/TGWallpaperInfo.h>

View File

@ -30,6 +30,7 @@
#import <LegacyComponents/TGMediaPickerGalleryVideoItemView.h> #import <LegacyComponents/TGMediaPickerGalleryVideoItemView.h>
#import <LegacyComponents/TGModernGalleryVideoView.h> #import <LegacyComponents/TGModernGalleryVideoView.h>
#import "TGMediaVideoConverter.h"
#import <LegacyComponents/TGMediaAssetImageSignals.h> #import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/PGPhotoEditorValues.h> #import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h> #import <LegacyComponents/TGVideoEditAdjustments.h>
@ -2404,6 +2405,10 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal; SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : video.videoDuration;
[signals addObject:[thumbnailSignal map:^id(UIImage *image) [signals addObject:[thumbnailSignal map:^id(UIImage *image)
{ {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@ -2411,6 +2416,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
dict[@"url"] = video.avAsset.URL; dict[@"url"] = video.avAsset.URL;
dict[@"previewImage"] = image; dict[@"previewImage"] = image;
dict[@"adjustments"] = adjustments; dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
dict[@"duration"] = @(duration);
if (adjustments.paintingData.stickers.count > 0) if (adjustments.paintingData.stickers.count > 0)
dict[@"stickers"] = adjustments.paintingData.stickers; dict[@"stickers"] = adjustments.paintingData.stickers;

View File

@ -749,6 +749,9 @@
NSArray *entities = [editingContext entitiesForItem:asset]; NSArray *entities = [editingContext entitiesForItem:asset];
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset]; id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
CGSize dimensions = asset.originalSize;
NSTimeInterval duration = asset.videoDuration;
[signals addObject:[inlineThumbnailSignal(asset) map:^id(UIImage *image) [signals addObject:[inlineThumbnailSignal(asset) map:^id(UIImage *image)
{ {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@ -757,6 +760,8 @@
dict[@"asset"] = asset; dict[@"asset"] = asset;
dict[@"previewImage"] = image; dict[@"previewImage"] = image;
dict[@"fileName"] = asset.fileName; dict[@"fileName"] = asset.fileName;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
dict[@"duration"] = @(duration);
if (adjustments.paintingData.stickers.count > 0) if (adjustments.paintingData.stickers.count > 0)
dict[@"stickers"] = adjustments.paintingData.stickers; dict[@"stickers"] = adjustments.paintingData.stickers;
@ -799,6 +804,10 @@
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal; SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration;
[signals addObject:[thumbnailSignal map:^id(UIImage *image) [signals addObject:[thumbnailSignal map:^id(UIImage *image)
{ {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@ -807,6 +816,8 @@
dict[@"asset"] = asset; dict[@"asset"] = asset;
dict[@"previewImage"] = image; dict[@"previewImage"] = image;
dict[@"adjustments"] = adjustments; dict[@"adjustments"] = adjustments;
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
dict[@"duration"] = @(duration);
if (adjustments.paintingData.stickers.count > 0) if (adjustments.paintingData.stickers.count > 0)
dict[@"stickers"] = adjustments.paintingData.stickers; dict[@"stickers"] = adjustments.paintingData.stickers;

View File

@ -23,6 +23,7 @@
+ (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions; + (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions;
+ (CGSize)_renderSizeWithCropSize:(CGSize)cropSize; + (CGSize)_renderSizeWithCropSize:(CGSize)cropSize;
+ (TGMediaVideoConversionPreset)presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments;
+ (CGSize)dimensionsFor:(CGSize)dimensions adjustments:(TGMediaVideoEditAdjustments *)adjustments preset:(TGMediaVideoConversionPreset)preset; + (CGSize)dimensionsFor:(CGSize)dimensions adjustments:(TGMediaVideoEditAdjustments *)adjustments preset:(TGMediaVideoConversionPreset)preset;
@end @end

View File

@ -116,7 +116,7 @@
return; return;
CGSize dimensions = [avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize; CGSize dimensions = [avAsset tracksWithMediaType:AVMediaTypeVideo].firstObject.naturalSize;
TGMediaVideoConversionPreset preset = adjustments.sendAsGif ? TGMediaVideoConversionPresetAnimation : [self _presetFromAdjustments:adjustments]; TGMediaVideoConversionPreset preset = adjustments.sendAsGif ? TGMediaVideoConversionPresetAnimation : [self presetFromAdjustments:adjustments];
if (!CGSizeEqualToSize(dimensions, CGSizeZero) && preset != TGMediaVideoConversionPresetAnimation && preset != TGMediaVideoConversionPresetVideoMessage) if (!CGSizeEqualToSize(dimensions, CGSizeZero) && preset != TGMediaVideoConversionPresetAnimation && preset != TGMediaVideoConversionPresetVideoMessage)
{ {
TGMediaVideoConversionPreset bestPreset = [self bestAvailablePresetForDimensions:dimensions]; TGMediaVideoConversionPreset bestPreset = [self bestAvailablePresetForDimensions:dimensions];
@ -524,7 +524,7 @@
NSError *error; NSError *error;
NSData *fileData = [NSData dataWithContentsOfURL:fileUrl options:NSDataReadingMappedIfSafe error:&error]; NSData *fileData = [NSData dataWithContentsOfURL:fileUrl options:NSDataReadingMappedIfSafe error:&error];
if (error == nil) if (error == nil)
return [SSignal single:[self _hashForVideoWithFileData:fileData timingData:timingData preset:[self _presetFromAdjustments:adjustments]]]; return [SSignal single:[self _hashForVideoWithFileData:fileData timingData:timingData preset:[self presetFromAdjustments:adjustments]]];
else else
return [SSignal fail:error]; return [SSignal fail:error];
} }
@ -572,7 +572,7 @@
return hash; return hash;
} }
+ (TGMediaVideoConversionPreset)_presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments + (TGMediaVideoConversionPreset)presetFromAdjustments:(TGMediaVideoEditAdjustments *)adjustments
{ {
TGMediaVideoConversionPreset preset = adjustments.preset; TGMediaVideoConversionPreset preset = adjustments.preset;
if (preset == TGMediaVideoConversionPresetCompressedDefault) if (preset == TGMediaVideoConversionPresetCompressedDefault)

View File

@ -4,8 +4,16 @@
@class TGViewController; @class TGViewController;
@class TGMenuSheetController; @class TGMenuSheetController;
typedef enum
{
TGPassportAttachIntentDefault,
TGPassportAttachIntentIdentityCard,
TGPassportAttachIntentSelfie,
TGPassportAttachIntentMultiple
} TGPassportAttachIntent;
@interface TGPassportAttachMenu : NSObject @interface TGPassportAttachMenu : NSObject
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title identity:(bool)identity selfie:(bool)selfie uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem; + (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem;
@end @end

View File

@ -27,7 +27,7 @@
@implementation TGPassportAttachMenu @implementation TGPassportAttachMenu
+ (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title identity:(bool)identity selfie:(bool)selfie uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem + (TGMenuSheetController *)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController menuController:(TGMenuSheetController *)menuController title:(NSString *)title intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect barButtonItem:(UIBarButtonItem *)barButtonItem
{ {
if (uploadAction == nil) if (uploadAction == nil)
return nil; return nil;
@ -51,7 +51,7 @@
__weak TGMenuSheetController *weakController = controller; __weak TGMenuSheetController *weakController = controller;
__weak TGViewController *weakParentController = parentController; __weak TGViewController *weakParentController = parentController;
TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithContext:context camera:true selfPortrait:selfie forProfilePhoto:false assetType:TGMediaAssetPhotoType saveEditedPhotos:false allowGrouping:false document:true]; TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithContext:context camera:true selfPortrait:intent == TGPassportAttachIntentSelfie forProfilePhoto:false assetType:TGMediaAssetPhotoType saveEditedPhotos:false allowGrouping:false document:true];
__weak TGAttachmentCarouselItemView *weakCarouselItem = carouselItem; __weak TGAttachmentCarouselItemView *weakCarouselItem = carouselItem;
carouselItem.onlyCrop = true; carouselItem.onlyCrop = true;
carouselItem.parentController = parentController; carouselItem.parentController = parentController;
@ -65,7 +65,7 @@
if (strongParentController == nil) if (strongParentController == nil)
return; return;
[TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context identity:identity uploadAction:uploadAction]; [TGPassportAttachMenu _displayCameraWithView:cameraView menuController:strongController parentController:strongParentController context:context intent:intent uploadAction:uploadAction];
}; };
carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles) carouselItem.sendPressed = ^(TGMediaAsset *currentItem, __unused bool asFiles)
{ {
@ -99,7 +99,7 @@
}]; }];
[itemViews addObject:galleryItem]; [itemViews addObject:galleryItem];
if (iosMajorVersion() >= 8 && !selfie) if (iosMajorVersion() >= 8 && intent != TGPassportAttachIntentSelfie)
{ {
TGMenuSheetButtonItemView *icloudItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileICloudDrive") type:TGMenuSheetButtonTypeDefault action:^ TGMenuSheetButtonItemView *icloudItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileICloudDrive") type:TGMenuSheetButtonTypeDefault action:^
{ {
@ -270,7 +270,7 @@
[parentController presentViewController:legacyCameraController animated:true completion:nil]; [parentController presentViewController:legacyCameraController animated:true completion:nil];
} }
+ (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context identity:(bool)identity uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction + (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context intent:(bool)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
{ {
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil]) if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil])
return; return;
@ -291,9 +291,9 @@
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager]; id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
if (cameraView.previewView != nil) if (cameraView.previewView != nil)
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false camera:cameraView.previewView.camera previewView:cameraView.previewView intent:identity ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent]; controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false camera:cameraView.previewView.camera previewView:cameraView.previewView intent:intent == TGPassportAttachIntentIdentityCard ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
else else
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false intent:identity ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent]; controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:false saveCapturedMedia:false intent:intent == TGPassportAttachIntentIdentityCard ? TGCameraControllerPassportIdIntent : TGCameraControllerPassportIntent];
controller.shouldStoreCapturedAssets = false; controller.shouldStoreCapturedAssets = false;

View File

@ -0,0 +1,25 @@
#import <Foundation/Foundation.h>
@interface TGPassportMRZ : NSObject
@property (nonatomic, readonly) NSString *documentType;
@property (nonatomic, readonly) NSString *documentSubtype;
@property (nonatomic, readonly) NSString *issuingCountry;
@property (nonatomic, readonly) NSString *lastName;
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *documentNumber;
@property (nonatomic, readonly) NSString *nationality;
@property (nonatomic, readonly) NSDate *birthDate;
@property (nonatomic, readonly) NSString *gender;
@property (nonatomic, readonly) NSDate *expiryDate;
@property (nonatomic, readonly) NSString *optional1;
@property (nonatomic, readonly) NSString *optional2;
@property (nonatomic, readonly) NSString *mrz;
+ (instancetype)parseLines:(NSArray *)lines;
@end
extern const NSUInteger TGPassportTD1Length;
extern const NSUInteger TGPassportTD23Length;

View File

@ -0,0 +1,316 @@
#import "TGPassportMRZ.h"
const NSUInteger TGPassportTD1Length = 30;
const NSUInteger TGPassportTD23Length = 44;
NSString *const TGPassportEmptyCharacter = @"<";
@implementation TGPassportMRZ
+ (instancetype)parseLines:(NSArray<NSString *> *)lines
{
if (lines.count == 2)
{
if (lines[0].length != TGPassportTD23Length || lines[1].length != TGPassportTD23Length)
return nil;
TGPassportMRZ *result = [[TGPassportMRZ alloc] init];
result->_documentType = [lines[0] substringToIndex:1];
result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]];
result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]];
NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter];
NSString *fullName = [[lines[0] substringWithRange:NSMakeRange(5, 39)] stringByTrimmingCharactersInSet:emptyCharacterSet];
NSArray *names = [fullName componentsSeparatedByString:@"<<"];
result->_lastName = [self nameString:names.firstObject];
result->_firstName = [self nameString:names.lastObject];
NSString *documentNumber = [self ensureNumberString:[lines[1] substringToIndex:9]];
NSInteger documentNumberCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(9, 1)]] integerValue];
if ([self isDataValid:documentNumber check:documentNumberCheck])
result->_documentNumber = documentNumber;
result->_nationality = [lines[1] substringWithRange:NSMakeRange(10, 3)];
NSString *birthDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(13, 6)]];
NSInteger birthDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(19, 1)]] integerValue];
if ([self isDataValid:birthDate check:birthDateCheck])
result->_birthDate = [self dateFromString:birthDate];
NSString *gender = [lines[1] substringWithRange:NSMakeRange(20, 1)];
if ([gender isEqualToString:TGPassportEmptyCharacter])
gender = nil;
result->_gender = gender;
NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(21, 6)]];
NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(27, 1)]] integerValue];
if ([self isDataValid:expiryDate check:expiryDateCheck])
result->_expiryDate = [self dateFromString:expiryDate];
NSString *optional1 = [lines[1] substringWithRange:NSMakeRange(28, 14)];
NSString *optional1CheckString = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(42, 1)]];
NSInteger optional1CheckValue = [optional1CheckString isEqualToString:TGPassportEmptyCharacter] ? 0 : [optional1CheckString integerValue];
if ([self isDataValid:optional1 check:optional1CheckValue])
result->_optional1 = [self cleanString:optional1];
NSString *data = [NSString stringWithFormat:@"%@%d%@%d%@%d%@%@", documentNumber, (int)documentNumberCheck, birthDate, (int)birthDateCheck, expiryDate, (int)expiryDateCheck, optional1, optional1CheckString];
NSInteger dataCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(43, 1)]] integerValue];
if ([self isDataValid:data check:dataCheck])
{
if ([result->_documentType isEqualToString:@"P"] && [result->_documentSubtype isEqualToString:@"N"] && [result->_issuingCountry isEqualToString:@"RUS"])
{
NSString *lastName = [self transliterateRussianMRZString:result->_lastName];
result->_lastName = [self transliterateRussianName:lastName];
NSString *firstName = [self transliterateRussianMRZString:result->_firstName];
result->_firstName = [self transliterateRussianName:firstName];
NSString *lastSeriesDigit = [optional1 substringToIndex:1];
NSMutableString *fullDocumentNo = [[NSMutableString alloc] init];
[fullDocumentNo insertString:[result->_documentNumber substringToIndex:3] atIndex:0];
[fullDocumentNo appendString:lastSeriesDigit];
[fullDocumentNo appendString:[result->_documentNumber substringFromIndex:3]];
result->_documentNumber = fullDocumentNo;
}
result->_mrz = [lines componentsJoinedByString:@"\n"];
return result;
}
}
else if (lines.count == 3)
{
if (lines[0].length != TGPassportTD1Length || lines[1].length != TGPassportTD1Length || lines[2].length != TGPassportTD1Length)
return nil;
TGPassportMRZ *result = [[TGPassportMRZ alloc] init];
result->_documentType = [lines[0] substringToIndex:1];
result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]];
result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]];
NSString *documentNumber = [self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(5, 9)]];
NSInteger documentNumberCheck = [[self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(14, 1)]] integerValue];
if ([self isDataValid:documentNumber check:documentNumberCheck])
result->_documentNumber = documentNumber;
NSString *optional1 = [lines[0] substringWithRange:NSMakeRange(15, 15)];
result->_optional1 = [self cleanString:optional1];
NSString *birthDate = [self ensureNumberString:[lines[1] substringToIndex:6]];
NSInteger birthDateCheck = [[lines[1] substringWithRange:NSMakeRange(6, 1)] integerValue];
if ([self isDataValid:birthDate check:birthDateCheck])
result->_birthDate = [self dateFromString:birthDate];
NSString *gender = [lines[1] substringWithRange:NSMakeRange(7, 1)];
if ([gender isEqualToString:TGPassportEmptyCharacter])
gender = nil;
result->_gender = gender;
NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(8, 6)]];
NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(14, 1)]] integerValue];
if ([self isDataValid:expiryDate check:expiryDateCheck])
result->_expiryDate = [self dateFromString:expiryDate];
result->_nationality = [lines[1] substringWithRange:NSMakeRange(15, 3)];
NSString *optional2 = [lines[1] substringWithRange:NSMakeRange(18, 11)];
result->_optional2 = optional2;
NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter];
NSString *fullName = [self ensureAlphaString:lines[2]];
fullName = [fullName stringByTrimmingCharactersInSet:emptyCharacterSet];
NSArray *names = [fullName componentsSeparatedByString:@"<<"];
result->_lastName = [self nameString:names.firstObject];
result->_firstName = [self nameString:names.lastObject];
result->_mrz = [lines componentsJoinedByString:@"\n"];
return result;
}
return nil;
}
+ (NSDateFormatter *)dateFormatter
{
static dispatch_once_t onceToken;
static NSDateFormatter *dateFormatter;
dispatch_once(&onceToken, ^
{
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"YYMMdd";
dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
});
return dateFormatter;
}
+ (NSDate *)dateFromString:(NSString *)string
{
return [[self dateFormatter] dateFromString:string];
}
+ (NSString *)cleanString:(NSString *)string
{
return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@""];
}
+ (NSString *)nameString:(NSString *)string
{
return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@" "];
}
+ (NSString *)ensureNumberString:(NSString *)string
{
return [[[[string stringByReplacingOccurrencesOfString:@"O" withString:@"0"] stringByReplacingOccurrencesOfString:@"U" withString:@"0"] stringByReplacingOccurrencesOfString:@"Q" withString:@"0"] stringByReplacingOccurrencesOfString:@"J" withString:@"0"];
}
+ (NSString *)ensureAlphaString:(NSString *)string
{
NSCharacterSet *validChars = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZ<"];
NSCharacterSet *invalidChars = [validChars invertedSet];
string = [string stringByReplacingOccurrencesOfString:@"0" withString:@"O"];
return [self string:string byReplacingCharactersInSet:invalidChars withString:TGPassportEmptyCharacter];
}
+ (NSString *)string:(NSString *)string byReplacingCharactersInSet:(NSCharacterSet *)charSet withString:(NSString *)aString {
NSMutableString *s = [NSMutableString stringWithCapacity:string.length];
for (NSUInteger i = 0; i < string.length; ++i) {
unichar c = [string characterAtIndex:i];
if (![charSet characterIsMember:c]) {
[s appendFormat:@"%C", c];
} else {
[s appendString:aString];
}
}
return s;
}
+ (NSString *)transliterateRussianMRZString:(NSString *)string
{
NSDictionary *map = @
{
@"A": @"А",
@"B": @"Б",
@"V": @"В",
@"G": @"Г",
@"D": @"Д",
@"E": @"Е",
@"2": @"Ё",
@"J": @"Ж",
@"Z": @"З",
@"I": @"И",
@"Q": @"Й",
@"K": @"К",
@"L": @"Л",
@"M": @"М",
@"N": @"Н",
@"O": @"О",
@"P": @"П",
@"R": @"Р",
@"S": @"С",
@"T": @"Т",
@"U": @"У",
@"F": @"Ф",
@"H": @"Х",
@"C": @"Ц",
@"3": @"Ч",
@"4": @"Ш",
@"W": @"Щ",
@"X": @"Ъ",
@"Y": @"Ы",
@"9": @"Ь",
@"6": @"Э",
@"7": @"Ю",
@"8": @"Я",
@" ": @" "
};
NSMutableString *result = [[NSMutableString alloc] init];
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop)
{
if (substring == nil)
return;
NSString *letter = map[substring];
if (letter != nil)
[result appendString:letter];
}];
return result;
}
+ (NSString *)transliterateRussianName:(NSString *)string
{
NSDictionary *map = @
{
@"А": @"A",
@"Б": @"B",
@"В": @"V",
@"Г": @"G",
@"Д": @"D",
@"Е": @"E",
@"Ё": @"E",
@"Ж": @"ZH",
@"З": @"Z",
@"И": @"I",
@"Й": @"I",
@"К": @"K",
@"Л": @"L",
@"М": @"M",
@"Н": @"N",
@"О": @"O",
@"П": @"P",
@"Р": @"R",
@"С": @"S",
@"Т": @"T",
@"У": @"U",
@"Ф": @"F",
@"Х": @"KH",
@"Ц": @"TS",
@"Ч": @"CH",
@"Ш": @"SH",
@"Щ": @"SHCH",
@"Ъ": @"IE",
@"Ы": @"Y",
@"Ь": @"",
@"Э": @"E",
@"Ю": @"IU",
@"Я": @"IA",
@" ": @" "
};
NSMutableString *result = [[NSMutableString alloc] init];
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop)
{
if (substring == nil)
return;
NSString *letter = map[substring];
if (letter != nil)
[result appendString:letter];
}];
return result;
}
+ (bool)isDataValid:(NSString *)data check:(NSInteger)check
{
int32_t sum = 0;
uint8_t w[3] = { 7, 3, 1 };
for (NSUInteger i = 0; i < data.length; i++)
{
unichar c = [data characterAtIndex:i];
NSInteger d = 0;
if (c >= '0' && c <= '9')
d = c - '0';
else if (c >= 'A' && c <= 'Z')
d = (10 + c) - 'A';
else if (c != '<')
return false;
sum += d * w[i % 3];
}
if (sum % 10 != check)
return false;
return true;
}
@end

View File

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SSignalKit.h>
@interface TGPassportOCR : NSObject
+ (SSignal *)recognizeMRZInImage:(UIImage *)image;
@end

View File

@ -0,0 +1,21 @@
#import "TGPassportOCR.h"
#import "TGPassportMRZ.h"
#import "ocr.h"
@implementation TGPassportOCR
+ (SSignal *)recognizeMRZInImage:(UIImage *)image
{
return [[SSignal defer:^SSignal *
{
CGRect boundingRect;
NSString *string = recognizeMRZ(image, &boundingRect);
NSArray *lines = [string componentsSeparatedByString:@"\n"];
TGPassportMRZ *mrz = [TGPassportMRZ parseLines:lines];
return [SSignal single:mrz];
}] startOn:[SQueue concurrentDefaultQueue]];
}
@end

View File

@ -0,0 +1,13 @@
#import <UIKit/UIKit.h>
@class TGPassportMRZ;
@interface TGPassportScanView : UIView
@property (nonatomic, copy) void (^finishedWithMRZ)(TGPassportMRZ *);
- (void)start;
- (void)stop;
- (void)pause;
@end

View File

@ -0,0 +1,98 @@
#import "TGPassportScanView.h"
#import "PGCamera.h"
#import "TGCameraPreviewView.h"
#import "TGPassportOCR.h"
#import "LegacyComponentsInternal.h"
#import "TGTimerTarget.h"
@interface TGPassportScanView ()
{
PGCamera *_camera;
TGCameraPreviewView *_previewView;
NSTimer *_timer;
SMetaDisposable *_ocrDisposable;
}
@end
@implementation TGPassportScanView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_camera = [[PGCamera alloc] initWithMode:PGCameraModePhoto position:PGCameraPositionRear];
_previewView = [[TGCameraPreviewView alloc] initWithFrame:self.bounds];
[self addSubview:_previewView];
[_camera attachPreviewView:_previewView];
_ocrDisposable = [[SMetaDisposable alloc] init];
}
return self;
}
- (void)dealloc
{
[_ocrDisposable dispose];
}
- (void)start
{
[_camera startCaptureForResume:false completion:nil];
_timer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(handleNextFrame) interval:1.0 repeat:false];
}
- (void)stop
{
[_camera stopCaptureForPause:false completion:nil];
_camera = nil;
[_timer invalidate];
_timer = nil;
}
- (void)pause
{
[_camera stopCaptureForPause:true completion:nil];
}
- (void)handleNextFrame
{
__weak TGPassportScanView *weakSelf = self;
[_camera captureNextFrameCompletion:^(UIImage *image)
{
[_ocrDisposable setDisposable:[[[TGPassportOCR recognizeMRZInImage:image] deliverOn:[SQueue mainQueue]] startWithNext:^(TGPassportMRZ *next)
{
__strong TGPassportScanView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (next != nil)
{
[strongSelf->_camera stopCaptureForPause:true completion:nil];
if (strongSelf.finishedWithMRZ != nil)
strongSelf.finishedWithMRZ(next);
}
else
{
strongSelf->_timer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(handleNextFrame) interval:0.45 repeat:false];
}
}]];
}];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_previewView.frame = self.bounds;
}
@end

550
LegacyComponents/fast-edge.cpp Executable file
View File

@ -0,0 +1,550 @@
/*
FAST-EDGE
Copyright (c) 2009 Benjamin C. Haynor
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "fast-edge.h"
#define LOW_THRESHOLD_PERCENTAGE 0.8 // percentage of the high threshold value that the low threshold shall be set at
#define PI 3.14159265
#define HIGH_THRESHOLD_PERCENTAGE 0.10 // percentage of pixels that meet the high threshold - for example 0.15 will ensure that at least 15% of edge pixels are considered to meet the high threshold
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
#define max(X,Y) ((X) < (Y) ? (Y) : (X))
namespace ocr{
/*
CANNY EDGE DETECT
DOES NOT PERFORM NOISE REDUCTION - PERFORM NOISE REDUCTION PRIOR TO USE
Noise reduction omitted, as some applications benefit from morphological operations such as opening or closing as opposed to Gaussian noise reduction
If your application always takes the same size input image, uncomment the definitions of WIDTH and HEIGHT in the header file and define them to the size of your input image,
otherwise the required intermediate arrays will be dynamically allocated.
If WIDTH and HEIGHT are defined, the arrays will be allocated in the compiler directive that follows:
*/
#ifdef WIDTH
int g[WIDTH * HEIGHT], dir[WIDTH * HEIGHT] = {0};
unsigned char img_scratch_data[WIDTH * HEIGHT] = {0};
#endif
void canny_edge_detect(struct image * img_in, struct image * img_out) {
struct image img_scratch;
int high, low;
#ifndef WIDTH
int * g = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
int * dir = (int*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(int));
unsigned char * img_scratch_data = (unsigned char*)calloc(static_cast<size_t>(img_in->width*img_in->height), sizeof(unsigned char));
#endif
img_scratch.width = img_in->width;
img_scratch.height = img_in->height;
img_scratch.pixel_data = img_scratch_data;
calc_gradient_sobel(img_in, g, dir);
//printf("*** performing non-maximum suppression ***\n");
non_max_suppression(&img_scratch, g, dir);
estimate_threshold(&img_scratch, &high, &low);
hysteresis(high, low, &img_scratch, img_out);
#ifndef WIDTH
free(g);
free(dir);
free(img_scratch_data);
#endif
}
/*
GAUSSIAN_NOISE_ REDUCE
apply 5x5 Gaussian convolution filter, shrinks the image by 4 pixels in each direction, using Gaussian filter found here:
http://en.wikipedia.org/wiki/Canny_edge_detector
*/
void gaussian_noise_reduce(struct image * img_in, struct image * img_out)
{
#ifdef CLOCK
clock_t start = clock();
#endif
int w, h, x, y, max_x, max_y;
w = img_in->width;
h = img_in->height;
img_out->width = w;
img_out->height = h;
max_x = w - 2;
max_y = w * (h - 2);
for (y = w * 2; y < max_y; y += w) {
for (x = 2; x < max_x; x++) {
img_out->pixel_data[x + y] = (2 * img_in->pixel_data[x + y - 2 - w - w] +
4 * img_in->pixel_data[x + y - 1 - w - w] +
5 * img_in->pixel_data[x + y - w - w] +
4 * img_in->pixel_data[x + y + 1 - w - w] +
2 * img_in->pixel_data[x + y + 2 - w - w] +
4 * img_in->pixel_data[x + y - 2 - w] +
9 * img_in->pixel_data[x + y - 1 - w] +
12 * img_in->pixel_data[x + y - w] +
9 * img_in->pixel_data[x + y + 1 - w] +
4 * img_in->pixel_data[x + y + 2 - w] +
5 * img_in->pixel_data[x + y - 2] +
12 * img_in->pixel_data[x + y - 1] +
15 * img_in->pixel_data[x + y] +
12 * img_in->pixel_data[x + y + 1] +
5 * img_in->pixel_data[x + y + 2] +
4 * img_in->pixel_data[x + y - 2 + w] +
9 * img_in->pixel_data[x + y - 1 + w] +
12 * img_in->pixel_data[x + y + w] +
9 * img_in->pixel_data[x + y + 1 + w] +
4 * img_in->pixel_data[x + y + 2 + w] +
2 * img_in->pixel_data[x + y - 2 + w + w] +
4 * img_in->pixel_data[x + y - 1 + w + w] +
5 * img_in->pixel_data[x + y + w + w] +
4 * img_in->pixel_data[x + y + 1 + w + w] +
2 * img_in->pixel_data[x + y + 2 + w + w]) / 159;
}
}
#ifdef CLOCK
printf("Gaussian noise reduction - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
/*
CALC_GRADIENT_SOBEL
calculates the result of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
*/
/*void calc_gradient_sobel(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {*/
void calc_gradient_sobel(struct image * img_in, int g[], int dir[]) {
#ifdef CLOCK
clock_t start = clock();
#endif
int w, h, x, y, max_x, max_y, g_x, g_y;
float g_div;
w = img_in->width;
h = img_in->height;
max_x = w - 3;
max_y = w * (h - 3);
for (y = w * 3; y < max_y; y += w) {
for (x = 3; x < max_x; x++) {
g_x = (2 * img_in->pixel_data[x + y + 1]
+ img_in->pixel_data[x + y - w + 1]
+ img_in->pixel_data[x + y + w + 1]
- 2 * img_in->pixel_data[x + y - 1]
- img_in->pixel_data[x + y - w - 1]
- img_in->pixel_data[x + y + w - 1]);
g_y = 2 * img_in->pixel_data[x + y - w]
+ img_in->pixel_data[x + y - w + 1]
+ img_in->pixel_data[x + y - w - 1]
- 2 * img_in->pixel_data[x + y + w]
- img_in->pixel_data[x + y + w + 1]
- img_in->pixel_data[x + y + w - 1];
#ifndef ABS_APPROX
g[x + y] = sqrt(g_x * g_x + g_y * g_y);
#endif
#ifdef ABS_APPROX
g[x + y] = abs(g_x[x + y]) + abs(g_y[x + y]);
#endif
if (g_x == 0) {
dir[x + y] = 2;
} else {
g_div = g_y / (float) g_x;
/* the following commented-out code is slightly faster than the code that follows, but is a slightly worse approximation for determining the edge direction angle
if (g_div < 0) {
if (g_div < -1) {
dir[n] = 0;
} else {
dir[n] = 1;
}
} else {
if (g_div > 1) {
dir[n] = 0;
} else {
dir[n] = 3;
}
}
*/
if (g_div < 0) {
if (g_div < -2.41421356237) {
dir[x + y] = 0;
} else {
if (g_div < -0.414213562373) {
dir[x + y] = 1;
} else {
dir[x + y] = 2;
}
}
} else {
if (g_div > 2.41421356237) {
dir[x + y] = 0;
} else {
if (g_div > 0.414213562373) {
dir[x + y] = 3;
} else {
dir[x + y] = 2;
}
}
}
}
}
}
#ifdef CLOCK
printf("Calculate gradient Sobel - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
/*
CALC_GRADIENT_SCHARR
calculates the result of the Scharr version of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle
may have better rotational symmetry
*/
void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {
#ifdef CLOCK
clock_t start = clock();
#endif
int w, h, x, y, max_x, max_y, n;
float g_div;
w = img_in->width;
h = img_in->height;
max_x = w - 1;
max_y = w * (h - 1);
n = 0;
for (y = w; y < max_y; y += w) {
for (x = 1; x < max_x; x++) {
g_x[n] = (10 * img_in->pixel_data[x + y + 1]
+ 3 * img_in->pixel_data[x + y - w + 1]
+ 3 * img_in->pixel_data[x + y + w + 1]
- 10 * img_in->pixel_data[x + y - 1]
- 3 * img_in->pixel_data[x + y - w - 1]
- 3 * img_in->pixel_data[x + y + w - 1]);
g_y[n] = 10 * img_in->pixel_data[x + y - w]
+ 3 * img_in->pixel_data[x + y - w + 1]
+ 3 * img_in->pixel_data[x + y - w - 1]
- 10 * img_in->pixel_data[x + y + w]
- 3 * img_in->pixel_data[x + y + w + 1]
- 3 * img_in->pixel_data[x + y + w - 1];
#ifndef ABS_APPROX
g[n] = sqrt(g_x[n] * g_x[n] + g_y[n] * g_y[n]);
#endif
#ifdef ABS_APPROX
g[n] = abs(g_x[n]) + abs(g_y[n]);
#endif
if (g_x[n] == 0) {
dir[n] = 2;
} else {
g_div = g_y[n] / (float) g_x[n];
if (g_div < 0) {
if (g_div < -2.41421356237) {
dir[n] = 0;
} else {
if (g_div < -0.414213562373) {
dir[n] = 1;
} else {
dir[n] = 2;
}
}
} else {
if (g_div > 2.41421356237) {
dir[n] = 0;
} else {
if (g_div > 0.414213562373) {
dir[n] = 3;
} else {
dir[n] = 2;
}
}
}
}
n++;
}
}
#ifdef CLOCK
printf("Calculate gradient Scharr - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
/*
NON_MAX_SUPPRESSION
using the estimates of the Gx and Gy image gradients and the edge direction angle determines whether the magnitude of the gradient assumes a local maximum in the gradient direction
if the rounded edge direction angle is 0 degrees, checks the north and south directions
if the rounded edge direction angle is 45 degrees, checks the northwest and southeast directions
if the rounded edge direction angle is 90 degrees, checks the east and west directions
if the rounded edge direction angle is 135 degrees, checks the northeast and southwest directions
*/
void non_max_suppression(struct image * img, int g[], int dir[]) {//float theta[]) {
#ifdef CLOCK
clock_t start = clock();
#endif
int w, h, x, y, max_x, max_y;
w = img->width;
h = img->height;
max_x = w;
max_y = w * h;
for (y = 0; y < max_y; y += w) {
for (x = 0; x < max_x; x++) {
switch (dir[x + y]) {
case 0:
if(x+y-w-1<0){
continue;
}
if (g[x + y] > g[x + y - w] && g[x + y] > g[x + y + w]) {
if (g[x + y] > 255) {
img->pixel_data[x + y] = 0xFF;
} else {
img->pixel_data[x + y] = g[x + y];
}
} else {
img->pixel_data[x + y] = 0x00;
}
break;
case 1:
if(x+y-w-1<0){
continue;
}
if (g[x + y] > g[x + y - w - 1] && g[x + y] > g[x + y + w + 1]) {
if (g[x + y] > 255) {
img->pixel_data[x + y] = 0xFF;
} else {
img->pixel_data[x + y] = g[x + y];
}
} else {
img->pixel_data[x + y] = 0x00;
}
break;
case 2:
if (g[x + y] > g[x + y - 1] && g[x + y] > g[x + y + 1]) {
if (g[x + y] > 255) {
img->pixel_data[x + y] = 0xFF;
} else {
img->pixel_data[x + y] = g[x + y];
}
} else {
img->pixel_data[x + y] = 0x00;
}
break;
case 3:
if(x+y-w-1<0){
continue;
}
if (g[x + y] > g[x + y - w + 1] && g[x + y] > g[x + y + w - 1]) {
if (g[x + y] > 255) {
img->pixel_data[x + y] = 0xFF;
} else {
img->pixel_data[x + y] = g[x + y];
}
} else {
img->pixel_data[x + y] = 0x00;
}
break;
default:
printf("ERROR - direction outside range 0 to 3");
break;
}
}
}
#ifdef CLOCK
printf("Non-maximum suppression - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
/*
ESTIMATE_THRESHOLD
estimates hysteresis threshold, assuming that the top X% (as defined by the HIGH_THRESHOLD_PERCENTAGE) of edge pixels with the greatest intesity are true edges
and that the low threshold is equal to the quantity of the high threshold plus the total number of 0s at the low end of the histogram divided by 2
*/
void estimate_threshold(struct image * img, int * high, int * low) {
#ifdef CLOCK
clock_t start = clock();
#endif
int i, max, pixels, high_cutoff;
int histogram[256];
max = img->width * img->height;
for (i = 0; i < 256; i++) {
histogram[i] = 0;
}
for (i = 0; i < max; i++) {
histogram[img->pixel_data[i]]++;
}
pixels = (max - histogram[0]) * HIGH_THRESHOLD_PERCENTAGE;
high_cutoff = 0;
i = 255;
while (high_cutoff < pixels) {
high_cutoff += histogram[i];
i--;
}
*high = i;
i = 1;
while (histogram[i] == 0) {
i++;
}
*low = (*high + i) * LOW_THRESHOLD_PERCENTAGE;
#ifdef PRINT_HISTOGRAM
for (i = 0; i < 256; i++) {
printf("i %d count %d\n", i, histogram[i]);
}
#endif
#ifdef CLOCK
printf("Estimate threshold - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
void hysteresis (int high, int low, struct image * img_in, struct image * img_out)
{
#ifdef CLOCK
clock_t start = clock();
#endif
int x, y, n, max;
max = img_in->width * img_in->height;
for (n = 0; n < max; n++) {
img_out->pixel_data[n] = 0x00;
}
for (y=0; y < img_out->height; y++) {
for (x=0; x < img_out->width; x++) {
if (img_in->pixel_data[y * img_out->width + x] >= high) {
trace (x, y, low, img_in, img_out);
}
}
}
#ifdef CLOCK
printf("Hysteresis - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
int trace(int x, int y, int low, struct image * img_in, struct image * img_out)
{
int y_off, x_off;//, flag;
if (img_out->pixel_data[y * img_out->width + x] == 0)
{
img_out->pixel_data[y * img_out->width + x] = 0xFF;
for (y_off = -1; y_off <=1; y_off++)
{
for(x_off = -1; x_off <= 1; x_off++)
{
if (!(y == 0 && x_off == 0) && range(img_in, x + x_off, y + y_off) && img_in->pixel_data[(y + y_off) * img_out->width + x + x_off] >= low) {
if (trace(x + x_off, y + y_off, low, img_in, img_out))
{
return(1);
}
}
}
}
return(1);
}
return(0);
}
int range(struct image * img, int x, int y)
{
if ((x < 0) || (x >= img->width)) {
return(0);
}
if ((y < 0) || (y >= img->height)) {
return(0);
}
return(1);
}
void dilate_1d_h(struct image * img, struct image * img_out) {
int x, y, offset, y_max;
y_max = img->height * (img->width - 2);
for (y = 2 * img->width; y < y_max; y += img->width) {
for (x = 2; x < img->width - 2; x++) {
offset = x + y;
img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
}
}
}
void dilate_1d_v(struct image * img, struct image * img_out) {
int x, y, offset, y_max;
y_max = img->height * (img->width - 2);
for (y = 2 * img->width; y < y_max; y += img->width) {
for (x = 2; x < img->width - 2; x++) {
offset = x + y;
img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
}
}
}
void erode_1d_h(struct image * img, struct image * img_out) {
int x, y, offset, y_max;
y_max = img->height * (img->width - 2);
for (y = 2 * img->width; y < y_max; y += img->width) {
for (x = 2; x < img->width - 2; x++) {
offset = x + y;
img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]);
}
}
}
void erode_1d_v(struct image * img, struct image * img_out) {
int x, y, offset, y_max;
y_max = img->height * (img->width - 2);
for (y = 2 * img->width; y < y_max; y += img->width) {
for (x = 2; x < img->width - 2; x++) {
offset = x + y;
img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]);
}
}
}
void erode(struct image * img_in, struct image * img_scratch, struct image * img_out) {
#ifdef CLOCK
clock_t start = clock();
#endif
erode_1d_h(img_in, img_scratch);
erode_1d_v(img_scratch, img_out);
#ifdef CLOCK
printf("Erosion - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out) {
#ifdef CLOCK
clock_t start = clock();
#endif
dilate_1d_h(img_in, img_scratch);
dilate_1d_v(img_scratch, img_out);
#ifdef CLOCK
printf("Dilation - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
#ifdef CLOCK
clock_t start = clock();
#endif
erode(img_in, img_scratch, img_scratch2);
dilate(img_scratch2, img_scratch, img_out);
#ifdef CLOCK
printf("Morphological opening - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) {
#ifdef CLOCK
clock_t start = clock();
#endif
dilate(img_in, img_scratch, img_scratch2);
erode(img_scratch2, img_scratch, img_out);
#ifdef CLOCK
printf("Morphological closing - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
#endif
}
}

61
LegacyComponents/fast-edge.h Executable file
View File

@ -0,0 +1,61 @@
/*
FAST-EDGE
Copyright (c) 2009 Benjamin C. Haynor
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _FASTEDGE
#define _FASTEDGE
namespace ocr{
//#define WIDTH 640 // uncomment to define width for situations where width is always known
//#define HEIGHT 480 // uncomment to define heigh for situations where height is always known
//#define CLOCK // uncomment to show running times of image processing functions (in seconds)
//#define ABS_APPROX // uncomment to use the absolute value approximation of sqrt(Gx ^ 2 + Gy ^2)
//#define PRINT_HISTOGRAM // uncomment to print the histogram used to estimate the threshold
struct image {
int width;
int height;
unsigned char * pixel_data;
};
void canny_edge_detect(struct image * img_in, struct image * img_out);
void gaussian_noise_reduce(struct image * img_in, struct image * img_out);
void calc_gradient_sobel(struct image * img_in, int g[], int dir[]);
void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]);
void non_max_suppression(struct image * img, int g[], int dir[]);
void estimate_threshold(struct image * img, int * high, int * low);
void hysteresis (int high, int low, struct image * img_in, struct image * img_out);
int trace (int x, int y, int low, struct image * img_in, struct image * img_out);
int range (struct image * img, int x, int y);
void dilate_1d_h(struct image * img, struct image * img_out);
void dilate_1d_v(struct image * img, struct image * img_out);
void erode_1d_h(struct image * img, struct image * img_out);
void erode_1d_v(struct image * img, struct image * img_out);
void erode(struct image * img_in, struct image * img_scratch, struct image * img_out);
void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out);
void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out);
void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out);
}
#endif

357
LegacyComponents/genann.c Executable file
View File

@ -0,0 +1,357 @@
/*
* GENANN - Minimal C Artificial Neural Network
*
* Copyright (c) 2015, 2016 Lewis Van Winkle
*
* http://CodePlea.com
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgement in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
*/
#include "genann.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <stdio.h>
#define LOOKUP_SIZE 4096
double genann_act_sigmoid(double a) {
if (a < -45.0) return 0;
if (a > 45.0) return 1;
return 1.0 / (1 + exp(-a));
}
double genann_act_sigmoid_cached(double a) {
/* If you're optimizing for memory usage, just
* delete this entire function and replace references
* of genann_act_sigmoid_cached to genann_act_sigmoid
*/
const double min = -15.0;
const double max = 15.0;
static double interval;
static int initialized = 0;
static double lookup[LOOKUP_SIZE];
/* Calculate entire lookup table on first run. */
if (!initialized) {
interval = (max - min) / LOOKUP_SIZE;
int i;
for (i = 0; i < LOOKUP_SIZE; ++i) {
lookup[i] = genann_act_sigmoid(min + interval * i);
}
/* This is down here to make this thread safe. */
initialized = 1;
}
int i;
i = (int)((a-min)/interval+0.5);
if (i <= 0) return lookup[0];
if (i >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE-1];
return lookup[i];
}
double genann_act_threshold(double a) {
return a > 0;
}
double genann_act_linear(double a) {
return a;
}
genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) {
if (hidden_layers < 0) return 0;
if (inputs < 1) return 0;
if (outputs < 1) return 0;
if (hidden_layers > 0 && hidden < 1) return 0;
const int hidden_weights = hidden_layers ? (inputs+1) * hidden + (hidden_layers-1) * (hidden+1) * hidden : 0;
const int output_weights = (hidden_layers ? (hidden+1) : (inputs+1)) * outputs;
const int total_weights = (hidden_weights + output_weights);
const int total_neurons = (inputs + hidden * hidden_layers + outputs);
/* Allocate extra size for weights, outputs, and deltas. */
const int size = sizeof(genann) + sizeof(double) * (total_weights + total_neurons + (total_neurons - inputs));
genann *ret = malloc(size);
if (!ret) return 0;
ret->inputs = inputs;
ret->hidden_layers = hidden_layers;
ret->hidden = hidden;
ret->outputs = outputs;
ret->total_weights = total_weights;
ret->total_neurons = total_neurons;
/* Set pointers. */
ret->weight = (double*)((char*)ret + sizeof(genann));
ret->output = ret->weight + ret->total_weights;
ret->delta = ret->output + ret->total_neurons;
genann_randomize(ret);
ret->activation_hidden = genann_act_sigmoid_cached;
ret->activation_output = genann_act_sigmoid_cached;
return ret;
}
genann *genann_read(FILE *in) {
int inputs, hidden_layers, hidden, outputs;
fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs);
genann *ann = genann_init(inputs, hidden_layers, hidden, outputs);
int i;
for (i = 0; i < ann->total_weights; ++i) {
fscanf(in, " %le", ann->weight + i);
}
return ann;
}
genann *genann_copy(genann const *ann) {
const int size = sizeof(genann) + sizeof(double) * (ann->total_weights + ann->total_neurons + (ann->total_neurons - ann->inputs));
genann *ret = malloc(size);
if (!ret) return 0;
memcpy(ret, ann, size);
/* Set pointers. */
ret->weight = (double*)((char*)ret + sizeof(genann));
ret->output = ret->weight + ret->total_weights;
ret->delta = ret->output + ret->total_neurons;
return ret;
}
void genann_randomize(genann *ann) {
int i;
for (i = 0; i < ann->total_weights; ++i) {
double r = GENANN_RANDOM();
/* Sets weights from -0.5 to 0.5. */
ann->weight[i] = r - 0.5;
}
}
void genann_free(genann *ann) {
/* The weight, output, and delta pointers go to the same buffer. */
free(ann);
}
double const *genann_run(genann const *ann, double const *inputs) {
double const *w = ann->weight;
double *o = ann->output + ann->inputs;
double const *i = ann->output;
/* Copy the inputs to the scratch area, where we also store each neuron's
* output, for consistency. This way the first layer isn't a special case. */
memcpy(ann->output, inputs, sizeof(double) * ann->inputs);
int h, j, k;
const genann_actfun act = ann->activation_hidden;
const genann_actfun acto = ann->activation_output;
/* Figure hidden layers, if any. */
for (h = 0; h < ann->hidden_layers; ++h) {
for (j = 0; j < ann->hidden; ++j) {
double sum = 0;
for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) {
if (k == 0) {
sum += *w++ * -1.0;
} else {
sum += *w++ * i[k-1];
}
}
*o++ = act(sum);
}
i += (h == 0 ? ann->inputs : ann->hidden);
}
double const *ret = o;
/* Figure output layer. */
for (j = 0; j < ann->outputs; ++j) {
double sum = 0;
for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) {
if (k == 0) {
sum += *w++ * -1.0;
} else {
sum += *w++ * i[k-1];
}
}
*o++ = acto(sum);
}
/* Sanity check that we used all weights and wrote all outputs. */
assert(w - ann->weight == ann->total_weights);
assert(o - ann->output == ann->total_neurons);
return ret;
}
void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate) {
/* To begin with, we must run the network forward. */
genann_run(ann, inputs);
int h, j, k;
/* First set the output layer deltas. */
{
double const *o = ann->output + ann->inputs + ann->hidden * ann->hidden_layers; /* First output. */
double *d = ann->delta + ann->hidden * ann->hidden_layers; /* First delta. */
double const *t = desired_outputs; /* First desired output. */
/* Set output layer deltas. */
if (ann->activation_output == genann_act_linear) {
for (j = 0; j < ann->outputs; ++j) {
*d++ = *t++ - *o++;
}
} else {
for (j = 0; j < ann->outputs; ++j) {
*d++ = (*t - *o) * *o * (1.0 - *o);
++o; ++t;
}
}
}
/* Set hidden layer deltas, start on last layer and work backwards. */
/* Note that loop is skipped in the case of hidden_layers == 0. */
for (h = ann->hidden_layers - 1; h >= 0; --h) {
/* Find first output and delta in this layer. */
double const *o = ann->output + ann->inputs + (h * ann->hidden);
double *d = ann->delta + (h * ann->hidden);
/* Find first delta in following layer (which may be hidden or output). */
double const * const dd = ann->delta + ((h+1) * ann->hidden);
/* Find first weight in following layer (which may be hidden or output). */
double const * const ww = ann->weight + ((ann->inputs+1) * ann->hidden) + ((ann->hidden+1) * ann->hidden * (h));
for (j = 0; j < ann->hidden; ++j) {
double delta = 0;
for (k = 0; k < (h == ann->hidden_layers-1 ? ann->outputs : ann->hidden); ++k) {
const double forward_delta = dd[k];
const int windex = k * (ann->hidden + 1) + (j + 1);
const double forward_weight = ww[windex];
delta += forward_delta * forward_weight;
}
*d = *o * (1.0-*o) * delta;
++d; ++o;
}
}
/* Train the outputs. */
{
/* Find first output delta. */
double const *d = ann->delta + ann->hidden * ann->hidden_layers; /* First output delta. */
/* Find first weight to first output delta. */
double *w = ann->weight + (ann->hidden_layers
? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * ann->hidden * (ann->hidden_layers-1))
: (0));
/* Find first output in previous layer. */
double const * const i = ann->output + (ann->hidden_layers
? (ann->inputs + (ann->hidden) * (ann->hidden_layers-1))
: 0);
/* Set output layer weights. */
for (j = 0; j < ann->outputs; ++j) {
for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) {
if (k == 0) {
*w++ += *d * learning_rate * -1.0;
} else {
*w++ += *d * learning_rate * i[k-1];
}
}
++d;
}
assert(w - ann->weight == ann->total_weights);
}
/* Train the hidden layers. */
for (h = ann->hidden_layers - 1; h >= 0; --h) {
/* Find first delta in this layer. */
double const *d = ann->delta + (h * ann->hidden);
/* Find first input to this layer. */
double const *i = ann->output + (h
? (ann->inputs + ann->hidden * (h-1))
: 0);
/* Find first weight to this layer. */
double *w = ann->weight + (h
? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * (ann->hidden) * (h-1))
: 0);
for (j = 0; j < ann->hidden; ++j) {
for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) {
if (k == 0) {
*w++ += *d * learning_rate * -1.0;
} else {
*w++ += *d * learning_rate * i[k-1];
}
}
++d;
}
}
}
void genann_write(genann const *ann, FILE *out) {
fprintf(out, "%d %d %d %d", ann->inputs, ann->hidden_layers, ann->hidden, ann->outputs);
int i;
for (i = 0; i < ann->total_weights; ++i) {
fprintf(out, " %.20e", ann->weight[i]);
}
}

110
LegacyComponents/genann.h Executable file
View File

@ -0,0 +1,110 @@
/*
* GENANN - Minimal C Artificial Neural Network
*
* Copyright (c) 2015, 2016 Lewis Van Winkle
*
* http://CodePlea.com
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgement in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
*/
#ifndef __GENANN_H__
#define __GENANN_H__
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef GENANN_RANDOM
/* We use the following for uniform random numbers between 0 and 1.
* If you have a better function, redefine this macro. */
#define GENANN_RANDOM() (((double)rand())/RAND_MAX)
#endif
typedef double (*genann_actfun)(double a);
typedef struct genann {
/* How many inputs, outputs, and hidden neurons. */
int inputs, hidden_layers, hidden, outputs;
/* Which activation function to use for hidden neurons. Default: gennann_act_sigmoid_cached*/
genann_actfun activation_hidden;
/* Which activation function to use for output. Default: gennann_act_sigmoid_cached*/
genann_actfun activation_output;
/* Total number of weights, and size of weights buffer. */
int total_weights;
/* Total number of neurons + inputs and size of output buffer. */
int total_neurons;
/* All weights (total_weights long). */
double *weight;
/* Stores input array and output of each neuron (total_neurons long). */
double *output;
/* Stores delta of each hidden and output neuron (total_neurons - inputs long). */
double *delta;
} genann;
/* Creates and returns a new ann. */
genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs);
/* Creates ANN from file saved with genann_write. */
genann *genann_read(FILE *in);
/* Sets weights randomly. Called by init. */
void genann_randomize(genann *ann);
/* Returns a new copy of ann. */
genann *genann_copy(genann const *ann);
/* Frees the memory used by an ann. */
void genann_free(genann *ann);
/* Runs the feedforward algorithm to calculate the ann's output. */
double const *genann_run(genann const *ann, double const *inputs);
/* Does a single backprop update. */
void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate);
/* Saves the ann. */
void genann_write(genann const *ann, FILE *out);
double genann_act_sigmoid(double a);
double genann_act_sigmoid_cached(double a);
double genann_act_threshold(double a);
double genann_act_linear(double a);
#ifdef __cplusplus
}
#endif
#endif /*__GENANN_H__*/

11
LegacyComponents/ocr.h Normal file
View File

@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
#ifdef __cplusplus
extern "C" {
#endif
NSString *recognizeMRZ(UIImage *input, CGRect *boundingRect);
#ifdef __cplusplus
}
#endif

645
LegacyComponents/ocr.mm Executable file
View File

@ -0,0 +1,645 @@
#import "ocr.h"
#include <vector>
#include <utility>
#include <string>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "fast-edge.h"
#include "genann.h"
#import "LegacyComponentsInternal.h"
#ifndef max
#define max(a, b) (a>b ? a : b)
#define min(a, b) (a<b ? a : b)
#endif
namespace ocr{
struct line{
double theta;
double r;
};
std::vector<line> detectLines(struct image* img, int threshold){
// The size of the neighbourhood in which to search for other local maxima
const int neighbourhoodSize = 4;
// How many discrete values of theta shall we check?
const int maxTheta = 180;
// Using maxTheta, work out the step
const double thetaStep = M_PI / maxTheta;
int width=img->width;
int height=img->height;
// Calculate the maximum height the hough array needs to have
int houghHeight = (int) (sqrt(2.0) * max(height, width)) / 2;
// Double the height of the hough array to cope with negative r values
int doubleHeight = 2 * houghHeight;
// Create the hough array
int* houghArray = new int[maxTheta*doubleHeight];
memset(houghArray, 0, sizeof(int)*maxTheta*doubleHeight);
// Find edge points and vote in array
int centerX = width / 2;
int centerY = height / 2;
// Count how many points there are
int numPoints = 0;
// cache the values of sin and cos for faster processing
double* sinCache = new double[maxTheta];
double* cosCache = new double[maxTheta];
for (int t = 0; t < maxTheta; t++) {
double realTheta = t * thetaStep;
sinCache[t] = sin(realTheta);
cosCache[t] = cos(realTheta);
}
// Now find edge points and update the hough array
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// Find non-black pixels
if ((img->pixel_data[y*width+x] & 0x000000ff) != 0) {
// Go through each value of theta
for (int t = 0; t < maxTheta; t++) {
//Work out the r values for each theta step
int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
// this copes with negative values of r
r += houghHeight;
if (r < 0 || r >= doubleHeight) continue;
// Increment the hough array
houghArray[t*doubleHeight+r]++;
}
numPoints++;
}
}
}
// Initialise the vector of lines that we'll return
std::vector<line> lines;
// Only proceed if the hough array is not empty
if (numPoints == 0){
delete[] houghArray;
delete[] sinCache;
delete[] cosCache;
return lines;
}
// Search for local peaks above threshold to draw
for (int t = 0; t < maxTheta; t++) {
//loop:
for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) {
// Only consider points above threshold
if (houghArray[t*doubleHeight+r] > threshold) {
int peak = houghArray[t*doubleHeight+r];
// Check that this peak is indeed the local maxima
for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) {
for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) {
int dt = t + dx;
int dr = r + dy;
if (dt < 0) dt = dt + maxTheta;
else if (dt >= maxTheta) dt = dt - maxTheta;
if (houghArray[dt*doubleHeight+dr] > peak) {
// found a bigger point nearby, skip
goto loop;
}
}
}
// calculate the true value of theta
double theta = t * thetaStep;
// add the line to the vector
line l={theta, (double)r-houghHeight};
lines.push_back(l);
}
loop:
continue;
}
}
delete[] houghArray;
delete[] sinCache;
delete[] cosCache;
return lines;
}
}
NSDictionary *findCornerPoints(UIImage *bitmap) {
CGImageRef imageRef = bitmap.CGImage;
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
struct ocr::image imgIn, imgOut;
imgIn.width = imgOut.width = width;
imgIn.height = imgOut.height = height;
imgIn.pixel_data = (uint8_t *)malloc(width * height);
imgOut.pixel_data = (uint8_t *)calloc(width * height, 1);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
for(unsigned int y=0;y<height;y++){
for(unsigned int x=0;x<width;x++){
uint32_t px = bitmapPixels[(bytesPerRow * y) + x];
imgIn.pixel_data[width*y+x]=(unsigned char) (((px & 0xFF) + ((px & 0xFF00) >> 8) + ((px & 0xFF0000) >> 16))/3);
}
}
ocr::canny_edge_detect(&imgIn, &imgOut);
std::vector<ocr::line> lines=ocr::detectLines(&imgOut, 100);
for(NSUInteger i = 0; i < width * height; i++) {
imgOut.pixel_data[i]/=2;
}
std::vector<std::vector<ocr::line>> parallelGroups;
for(int i = 0; i < 36; i++) {
parallelGroups.emplace_back();
}
ocr::line *left = NULL;
ocr::line *right = NULL;
ocr::line *top = NULL;
ocr::line *bottom = NULL;
for(std::vector<ocr::line>::iterator l = lines.begin(); l!= lines.end();) {
// remove lines at irrelevant angles
if(!(l->theta>M_PI*0.4 && l->theta<M_PI*0.6) && !(l->theta<M_PI*0.1 || l->theta>M_PI*0.9)){
l=lines.erase(l);
continue;
}
// remove vertical lines close to the middle of the image
if((l->theta<M_PI*0.1 || l->theta>M_PI*0.9) && (uint32_t)abs((int)l->r) < height / 4){
l=lines.erase(l);
continue;
}
// find the leftmost and rightmost lines
if(l->theta<M_PI*0.1 || l->theta>M_PI*0.9){
double rk=l->theta<0.5 ? 1.0 : -1.0;
if(!left || left->r>l->r*rk){
left=&*l;
}
if(!right || right->r<l->r*rk){
right=&*l;
}
}
// group parallel-ish lines with 5-degree increments
parallelGroups[(uint32_t)floor(l->theta / M_PI * 36)].push_back(*l);
++l;
}
// the text on the page tends to produce a lot of parallel lines - so we assume the top & bottom edges of the page
// are topmost & bottommost lines in the largest group of horizontal lines
std::vector<ocr::line>& largestParallelGroup=parallelGroups[0];
for(std::vector<std::vector<ocr::line>>::iterator group=parallelGroups.begin();group!=parallelGroups.end();++group){
if(largestParallelGroup.size()<group->size())
largestParallelGroup=*group;
}
for(std::vector<ocr::line>::iterator l=largestParallelGroup.begin();l!=largestParallelGroup.end();++l){
// If the image is horizontal, we assume it's just the data page or an ID card so we're going for the topmost line.
// If it's vertical, it likely contains both the data page and the page adjacent to it so we're going for the line that is closest to the center of the image.
// Nobody in their right mind is going to be taking vertical pictures of ID cards, right?
if(width>height){
if(!top || top->r>l->r){
top=&*l;
}
}else{
if(!top || fabs(l->r)<fabs(top->r)){
top=&*l;
}
}
if(!bottom || bottom->r<l->r){
bottom=&*l;
}
}
bool foundTopLeft=false, foundTopRight=false, foundBottomLeft=false, foundBottomRight=false;
NSMutableDictionary *points = [[NSMutableDictionary alloc] init];
if(top && bottom && left && right){
//LOGI("bottom theta %f", bottom->theta);
if(bottom->theta>1.65 || bottom->theta<1.55){
//LOGD("left: %f, right: %f\n", left->r, right->r);
double centerX=width/2.0;
double centerY=height/2.0;
double ltsin=sin(left->theta);
double ltcos=cos(left->theta);
double rtsin=sin(right->theta);
double rtcos=cos(right->theta);
double ttsin=sin(top->theta);
double ttcos=cos(top->theta);
double btsin=sin(bottom->theta);
double btcos=cos(bottom->theta);
for (int y = -((int)height)/4; y < (int)height; y++) {
int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
int ty = (int) (((top->r - ((lx - centerX) * ttcos)) / ttsin) + centerY);
if(ty==y){
points[@0]=@(lx);
points[@1]=@(y);
foundTopLeft=true;
if(foundTopRight)
break;
}
int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
ty = (int) (((top->r - ((rx - centerX) * ttcos)) / ttsin) + centerY);
if(ty==y){
points[@2]=@(rx);
points[@3]=@(y);
foundTopRight=true;
if(foundTopLeft)
break;
}
}
for (int y = height+height/3; y>=0; y--) {
int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX);
int by = (int) (((bottom->r - ((lx - centerX) * btcos)) / btsin) + centerY);
if(by==y){
points[@4]=@(lx);
points[@5]=@(y);
foundBottomLeft=true;
if(foundBottomRight)
break;
}
int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX);
by = (int) (((bottom->r - ((rx - centerX) * btcos)) / btsin) + centerY);
if(by==y){
points[@6]=@(rx);
points[@7]=@(y);
foundBottomRight=true;
if(foundBottomLeft)
break;
}
}
}else{
//LOGD("No perspective correction needed");
}
}
free(imgIn.pixel_data);
free(imgOut.pixel_data);
if(foundTopLeft && foundTopRight && foundBottomLeft && foundBottomRight) {
return points;
}
return nil;
}
NSArray *binarizeAndFindCharacters(UIImage *inBmp, UIImage **outBinaryImage) {
CGImageRef imageRef = inBmp.CGImage;
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
uint8_t *outPixels = (uint8_t *)malloc(width * height * 1);
uint32_t histogram[256]={0};
uint32_t intensitySum=0;
for(unsigned int y=0;y<height;y++){
for(unsigned int x=0;x<width;x++){
uint8_t *px = bitmapPixels + (bytesPerRow * y) + x * 4;
uint8_t r = *(px + 1);
uint8_t g = *(px + 2);
uint8_t b = *(px + 3);
int l = (r + g + b)/3.0;
outPixels[(width * y) + x]=l;
histogram[l]++;
intensitySum+=l;
}
}
uint32_t threshold=0;
double best_sigma = 0.0;
int first_class_pixel_count = 0;
int first_class_intensity_sum = 0;
for (int thresh = 0; thresh < 255; ++thresh) {
first_class_pixel_count += histogram[thresh];
first_class_intensity_sum += thresh * histogram[thresh];
double first_class_prob = first_class_pixel_count / (double) (width*height);
double second_class_prob = 1.0 - first_class_prob;
double first_class_mean = first_class_intensity_sum / (double) first_class_pixel_count;
double second_class_mean = (intensitySum - first_class_intensity_sum) / (double) ((width*height) - first_class_pixel_count);
double mean_delta = first_class_mean - second_class_mean;
double sigma = first_class_prob * second_class_prob * mean_delta * mean_delta;
if (sigma > best_sigma) {
best_sigma = sigma;
threshold = thresh;
}
}
for(unsigned int y=0;y<height;y++){
for(unsigned int x=0;x<width;x++){
uint8_t *px = bitmapPixels + (bytesPerRow * y) + x * 4;
uint8_t r = *(px + 1);
uint8_t g = *(px + 2);
uint8_t b = *(px + 3);
outPixels[(width * y) + x]=(r<threshold && g<threshold && b<threshold) ? (unsigned char)255 : (unsigned char)0;
}
}
// remove any single pixels without adjacent ones - these are usually noise
for(unsigned int y=height/2;y<height-1;y++){
for(unsigned int x=1;x<width-1;x++){
if(outPixels[width * y + x]>0
&& outPixels[width * y + x +1]==0
&& outPixels[width * y + x -1]==0
&& outPixels[width * (y + 1) + x]==0
&& outPixels[width * (y - 1) + x]==0){
outPixels[width * y + x]=0;
}
}
}
if (outBinaryImage != nil)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(outPixels, width, height, 8, width, colorSpace, kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
CGContextRelease(context);
*outBinaryImage = img;
}
// search from the bottom up for continuous areas of mostly empty pixels
unsigned int consecutiveEmptyRows=0;
std::vector<std::pair<unsigned int, unsigned int>> emptyAreaYs;
for(unsigned int y=height-1;y>=height/2;y--){
unsigned int consecutiveEmptyPixels=0;
unsigned int maxEmptyPixels=0;
for(unsigned int x=0;x<width;x++){
if(outPixels[width * y + x]==0){
consecutiveEmptyPixels++;
}else{
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
consecutiveEmptyPixels=0;
}
}
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
if(maxEmptyPixels>width/10*8){
consecutiveEmptyRows++;
}else if(consecutiveEmptyRows>0){
emptyAreaYs.emplace_back(y, y+consecutiveEmptyRows);
consecutiveEmptyRows=0;
}
}
NSMutableArray *result = [[NSMutableArray alloc] init];
// using the areas found above, do the same thing but horizontally and between them in an attempt to ultimately find the bounds of the MRZ characters
for(std::vector<std::pair<unsigned int, unsigned int>>::iterator p=emptyAreaYs.begin();p!=emptyAreaYs.end();++p){
std::vector<std::pair<unsigned int, unsigned int>>::iterator next=std::next(p);
if(next!=emptyAreaYs.end()){
unsigned int lineHeight=p->first-next->second;
// An MRZ line can't really be this thin so this probably isn't one
if(lineHeight<10)
continue;
unsigned int consecutiveEmptyCols=0;
std::vector<std::pair<unsigned int, unsigned int>> emptyAreaXs;
for(unsigned int x=0;x<width;x++){
unsigned int consecutiveEmptyPixels=0;
unsigned int maxEmptyPixels=0;
unsigned int bottomFilledPixels=0; // count these separately because we want those L's recognized correctly
for(unsigned int y=next->second;y<p->first;y++){
if(outPixels[width * y + x]==0){
consecutiveEmptyPixels++;
}else{
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
consecutiveEmptyPixels=0;
if(y>p->first-3)
bottomFilledPixels++;
}
}
maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels);
if(lineHeight-maxEmptyPixels<lineHeight/10 && bottomFilledPixels==0){
consecutiveEmptyCols++;
}else if(consecutiveEmptyCols>0){
emptyAreaXs.emplace_back(x-consecutiveEmptyCols, x);
consecutiveEmptyCols=0;
}
}
if(consecutiveEmptyCols>0){
emptyAreaXs.emplace_back(width-consecutiveEmptyCols, width);
}
if(emptyAreaXs.size()>30){
bool foundLeftPadding=false;
NSMutableArray *rects = [[NSMutableArray alloc] init];
for(std::vector<std::pair<unsigned int, unsigned int>>::iterator h=emptyAreaXs.begin();h!=emptyAreaXs.end();++h){
std::vector<std::pair<unsigned int, unsigned int>>::iterator nextH=std::next(h);
if(!foundLeftPadding && h->second-h->first>width/35){
foundLeftPadding=true;
}else if(foundLeftPadding && h->second-h->first>width/30){
if(rects.count>=30){
break;
}else{
// restart the search because now we've (hopefully) found the real padding
[rects removeAllObjects];
}
}
if(nextH!=emptyAreaXs.end() && foundLeftPadding){
unsigned int top=next->second;
unsigned int bottom=p->first;
// move the top and bottom edges towards each other as part of normalization
for(unsigned int y=top;y<bottom;y++){
bool found=false;
for(unsigned int x=h->second; x<nextH->first; x++){
if(outPixels[width * y + x]!=0){
top=y;
found=true;
break;
}
}
if(found)
break;
}
for(unsigned int y=bottom;y>top;y--){
bool found=false;
for(unsigned int x=h->second; x<nextH->first; x++){
if(outPixels[width * y + x]!=0){
bottom=y;
found=true;
break;
}
}
if(found)
break;
}
if(bottom-top<lineHeight/2)
continue;
if(rects.count < 44){
CGRect rect = CGRectMake(h->second, top, nextH->first - h->second, bottom - top);
[rects addObject:[NSValue valueWithCGRect:rect]];
}
}
}
[result addObject:rects];
if((rects.count>=44 && result.count == 2) || (rects.count>=30 && result.count==3)){
break;
}
}
}
}
free(outPixels);
if(result.count == 0)
return NULL;
return result;
}
NSString *performRecognition(UIImage *bitmap, int numRows, int numCols)
{
NSString *filePath = TGComponentsPathForResource(@"ocr_nn", @"bin");
NSData *nnData = [NSData dataWithContentsOfFile:filePath];
struct genann* ann=genann_init(150, 1, 90, 37);
memcpy(ann->weight, nnData.bytes, sizeof(double)*ann->total_weights);
NSMutableString *res = [[NSMutableString alloc] init];
const char* alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<";
CGImageRef imageRef = bitmap.CGImage;
uint32_t width = (uint32_t)CGImageGetWidth(imageRef);
uint32_t height = (uint32_t)CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 1, sizeof(unsigned char));
NSUInteger bytesPerPixel = 1;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
double nnInput[150];
for(int row=0;row<numRows;row++){
for(int col=0;col<numCols;col++){
unsigned int offX=static_cast<unsigned int>(col*10);
unsigned int offY=static_cast<unsigned int>(row*15);
for(unsigned int y=0;y<15;y++){
for(unsigned int x=0;x<10;x++){
nnInput[y*10+x]=(double)bitmapPixels[bytesPerRow * (offY+y) + offX + x]/255.0;
}
}
const double* nnOut=genann_run(ann, nnInput);
unsigned int bestIndex=0;
for(unsigned int i=0;i<37;i++){
if(nnOut[i]>nnOut[bestIndex])
bestIndex=i;
}
[res appendString:[NSString stringWithFormat:@"%c", alphabet[bestIndex]]];
}
if(row!=numRows-1)
[res appendString:@"\n"];
}
genann_free(ann);
return res;
}
UIImage *normalizeImage(UIImage *image)
{
if (image.imageOrientation == UIImageOrientationUp) return image;
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawInRect:(CGRect){0, 0, image.size}];
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalizedImage;
}
NSString *recognizeMRZ(UIImage *input, CGRect *outBoundingRect)
{
input = normalizeImage(input);
UIImage *binaryImage;
NSArray *charRects = binarizeAndFindCharacters(input, &binaryImage);
if (charRects.count == 0)
return nil;
uint32_t width = 10 * (int)[charRects.firstObject count];
uint32_t height = 15 * (int)charRects.count;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width, colorSpace, kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
int x, y = 0;
for (NSArray *line in charRects)
{
x = 0;
for (NSValue *v in line)
{
CGRect rect = v.CGRectValue;
CGRect dest = CGRectMake(x * 10, y * 15, 10, 15);
CGImageRef charImage = CGImageCreateWithImageInRect(binaryImage.CGImage, rect);
CGContextDrawImage(context, dest, charImage);
CGImageRelease(charImage);
x++;
}
y++;
}
CGImageRef charsImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *charsImage = [UIImage imageWithCGImage:charsImageRef];
CGImageRelease(charsImageRef);
NSString *result = performRecognition(charsImage, (int)charRects.count, (int)[charRects.firstObject count]);
if (result != nil && outBoundingRect != NULL)
{
CGRect firstRect = [[charRects.firstObject firstObject] CGRectValue];
firstRect.origin.y = input.size.height - firstRect.origin.y;
CGRect lastRect = [[charRects.lastObject lastObject] CGRectValue];
lastRect.origin.y = input.size.height - lastRect.origin.y;
CGRect boundingRect = CGRectMake(CGRectGetMinX(firstRect), CGRectGetMinY(firstRect), CGRectGetMaxX(lastRect) - CGRectGetMinX(firstRect), CGRectGetMaxY(lastRect) - CGRectGetMinY(firstRect));
*outBoundingRect = boundingRect;
}
return result;
}