├── .github ├── Images │ └── cropper.png └── README.md ├── Plugins ├── ImageCropper │ ├── Sprites │ │ ├── ImageCropperBg.psd │ │ ├── RotateImageButton.psd │ │ ├── FlipVerticalButton.psd │ │ ├── ImageCropperBorders.psd │ │ ├── ImageCropperCorners.psd │ │ ├── FlipHorizontalButton.psd │ │ ├── ImageCropperButtonsBg.psd │ │ ├── ImageCropperBordersThick.psd │ │ ├── ImageCropperBg.psd.meta │ │ ├── FlipHorizontalButton.psd.meta │ │ ├── FlipVerticalButton.psd.meta │ │ ├── ImageCropperBorders.psd.meta │ │ ├── ImageCropperButtonsBg.psd.meta │ │ ├── ImageCropperCorners.psd.meta │ │ ├── RotateImageButton.psd.meta │ │ └── ImageCropperBordersThick.psd.meta │ ├── ImageCropper.Runtime.asmdef │ ├── Scripts │ │ ├── ISelectionHandler.cs │ │ ├── ImageCropper.cs.meta │ │ ├── CircleGraphic.cs.meta │ │ ├── ISelectionHandler.cs.meta │ │ ├── ImageCropperUtils.cs.meta │ │ ├── NotchCompensator.cs.meta │ │ ├── EventSystemHandler.cs.meta │ │ ├── FadeOverlayGraphic.cs.meta │ │ ├── FontSizeSynchronizer.cs.meta │ │ ├── SelectionCornersFitter.cs.meta │ │ ├── SelectionResizeHandler.cs.meta │ │ ├── SizeChangeListener.cs.meta │ │ ├── SelectionMovementHandler.cs.meta │ │ ├── SelectionGraphicsSynchronizer.cs.meta │ │ ├── SizeChangeListener.cs │ │ ├── FontSizeSynchronizer.cs │ │ ├── SelectionGraphicsSynchronizer.cs │ │ ├── FadeOverlayGraphic.cs │ │ ├── SelectionCornersFitter.cs │ │ ├── EventSystemHandler.cs │ │ ├── NotchCompensator.cs │ │ ├── ImageCropperUtils.cs │ │ ├── SelectionMovementHandler.cs │ │ ├── CircleGraphic.cs │ │ ├── SelectionResizeHandler.cs │ │ └── ImageCropper.cs │ ├── ImageCropper.Runtime.asmdef.meta │ ├── README.txt.meta │ ├── Demo │ │ ├── DemoScene.unity.meta │ │ ├── ImageCropperDemo.cs.meta │ │ └── ImageCropperDemo.cs │ ├── Demo.meta │ ├── Resources.meta │ ├── Scripts.meta │ ├── Sprites.meta │ ├── README.txt │ └── Resources │ │ └── ImageCropper.prefab.meta └── ImageCropper.meta ├── LICENSE.txt.meta ├── package.json.meta ├── Plugins.meta ├── package.json └── LICENSE.txt /.github/Images/cropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/.github/Images/cropper.png -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBg.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/ImageCropperBg.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/ImageCropper.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ImageCropper.Runtime", 3 | "references": [ 4 | "Unity.InputSystem" 5 | ] 6 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/RotateImageButton.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/RotateImageButton.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/FlipVerticalButton.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/FlipVerticalButton.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBorders.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/ImageCropperBorders.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperCorners.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/ImageCropperCorners.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/FlipHorizontalButton.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/FlipHorizontalButton.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperButtonsBg.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/ImageCropperButtonsBg.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBordersThick.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityImageCropper/HEAD/Plugins/ImageCropper/Sprites/ImageCropperBordersThick.psd -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ISelectionHandler.cs: -------------------------------------------------------------------------------- 1 | namespace ImageCropperNamespace 2 | { 3 | public interface ISelectionHandler 4 | { 5 | void OnUpdate(); 6 | void Stop(); 7 | } 8 | } -------------------------------------------------------------------------------- /LICENSE.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 31196649e76bceb45ac455859dd196dd 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95fc38e7aab326846bb203396a300441 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13e4faf5101b7b64087d2a902385ce96 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/ImageCropper.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 58b8905714dedd04693f1fb3fd955437 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/README.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f2524c1e4c1b07d4892037793e8a9404 3 | timeCreated: 1563307881 4 | licenseType: Free 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Demo/DemoScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9c3a8b1d560a0146b500a8fc3b06487 3 | timeCreated: 1523817215 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/ImageCropper.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 28f5bc2030be6bd41a6066dd84d44902 3 | folderAsset: yes 4 | timeCreated: 1523020534 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Demo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0da2e9db10b6016438ab2af9dacde435 3 | folderAsset: yes 4 | timeCreated: 1523817013 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e8f5004af78dcf4283d0cf22bbb48a5 3 | folderAsset: yes 4 | timeCreated: 1523020546 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bd4231b281c79d24099b410dcffb0660 3 | folderAsset: yes 4 | timeCreated: 1523023285 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de22cfbccca16af4d878db0e649911b7 3 | folderAsset: yes 4 | timeCreated: 1523020570 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/README.txt: -------------------------------------------------------------------------------- 1 | = Image Cropper (v1.0.8) = 2 | 3 | Documentation: https://github.com/yasirkula/UnityImageCropper 4 | FAQ: https://github.com/yasirkula/UnityImageCropper#faq 5 | Example code: https://github.com/yasirkula/UnityImageCropper#example-code 6 | E-mail: yasirkula@gmail.com -------------------------------------------------------------------------------- /Plugins/ImageCropper/Resources/ImageCropper.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3ec8097647d9e54daa56be589f9a12b 3 | timeCreated: 1523020550 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ImageCropper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36771194e183b9644a5e8a7da8266967 3 | timeCreated: 1523026864 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Demo/ImageCropperDemo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69027feb3ce406745b1f9c00f61d4b70 3 | timeCreated: 1523818595 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/CircleGraphic.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 713600372b4defb458fcaf7bb47fedbc 3 | timeCreated: 1523363091 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ISelectionHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d78547850cdc3d0449c56b20bdebdbdf 3 | timeCreated: 1523306212 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ImageCropperUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c47fc29e548efd848bef67581c69904e 3 | timeCreated: 1523298806 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/NotchCompensator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8515777b12faab4aa32d26f9037af5c 3 | timeCreated: 1617958394 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/EventSystemHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a39d7b3bb056d340842cf64a2bdf500 3 | timeCreated: 1658741613 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/FadeOverlayGraphic.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8c3d44f0736eda4f83e452134543b3b 3 | timeCreated: 1523363091 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/FontSizeSynchronizer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c7e2faa58cd3d546baa167999dbf7d9 3 | timeCreated: 1523634406 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionCornersFitter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3f6e5203b9596d34c9c93b34dc5c2444 3 | timeCreated: 1523373445 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionResizeHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 493caa6d6a37d2843a59fcc5ac787ea8 3 | timeCreated: 1523026423 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SizeChangeListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ffb5840d0058c145bcff9f500ab3eae 3 | timeCreated: 1523045737 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionMovementHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 33348899dae70cc46b4343a8838195cf 3 | timeCreated: 1523023329 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionGraphicsSynchronizer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e2fed2a2c78b61c4e86034296678669c 3 | timeCreated: 1523270788 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.yasirkula.imagecropper", 3 | "displayName": "Image Cropper", 4 | "version": "1.0.8", 5 | "documentationUrl": "https://github.com/yasirkula/UnityImageCropper", 6 | "changelogUrl": "https://github.com/yasirkula/UnityImageCropper/releases", 7 | "licensesUrl": "https://github.com/yasirkula/UnityImageCropper/blob/master/LICENSE.txt", 8 | "description": "ImageCropper aims to be a powerful, customizable and easy-to-use image cropping solution for Unity 3D. It is created with Unity's UI system.", 9 | "hideInEditor": false 10 | } 11 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SizeChangeListener.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace ImageCropperNamespace 4 | { 5 | public class SizeChangeListener : MonoBehaviour 6 | { 7 | private RectTransform rectTransform; 8 | 9 | public System.Action onSizeChanged; 10 | 11 | private void Awake() 12 | { 13 | rectTransform = (RectTransform) transform; 14 | } 15 | 16 | private void Start() 17 | { 18 | OnRectTransformDimensionsChange(); 19 | } 20 | 21 | private void OnRectTransformDimensionsChange() 22 | { 23 | if( onSizeChanged != null && rectTransform != null ) 24 | onSizeChanged( rectTransform.rect.size ); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Süleyman Yasir KULA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/FontSizeSynchronizer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | 4 | namespace ImageCropperNamespace 5 | { 6 | public class FontSizeSynchronizer : MonoBehaviour 7 | { 8 | [SerializeField] 9 | private Text[] texts; 10 | 11 | private int[] initialBestFitSizes; 12 | private Canvas canvas; 13 | 14 | private void Awake() 15 | { 16 | if( texts.Length == 0 ) 17 | return; 18 | 19 | canvas = texts[0].canvas; 20 | 21 | initialBestFitSizes = new int[texts.Length]; 22 | for( int i = 0; i < texts.Length; i++ ) 23 | initialBestFitSizes[i] = texts[i].resizeTextMaxSize; 24 | } 25 | 26 | public void Synchronize() 27 | { 28 | if( canvas == null || !gameObject.activeInHierarchy ) 29 | return; 30 | 31 | int minSize = int.MaxValue; 32 | for( int i = 0; i < texts.Length; i++ ) 33 | { 34 | Text text = texts[i]; 35 | 36 | text.resizeTextMaxSize = initialBestFitSizes[i]; 37 | text.resizeTextForBestFit = true; 38 | text.cachedTextGenerator.Populate( text.text, text.GetGenerationSettings( text.rectTransform.rect.size ) ); 39 | 40 | int fontSize = text.cachedTextGenerator.fontSizeUsedForBestFit; 41 | if( fontSize < minSize ) 42 | minSize = fontSize; 43 | } 44 | 45 | int fontSizeScaled = (int) ( minSize / canvas.scaleFactor ); 46 | for( int i = 0; i < texts.Length; i++ ) 47 | { 48 | texts[i].fontSize = fontSizeScaled; 49 | texts[i].resizeTextForBestFit = false; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionGraphicsSynchronizer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace ImageCropperNamespace 4 | { 5 | public class SelectionGraphicsSynchronizer : MonoBehaviour 6 | { 7 | [SerializeField] 8 | private ImageCropper manager; 9 | 10 | [SerializeField] 11 | private RectTransform selectionBottomLeft; 12 | 13 | [SerializeField] 14 | private RectTransform selectionTopRight; 15 | 16 | private RectTransform viewport; 17 | private RectTransform selectionGraphics; 18 | 19 | private Vector2 bottomLeftPrevPosition, topRightPrevPosition; 20 | 21 | private void Awake() 22 | { 23 | viewport = manager.Viewport; 24 | selectionGraphics = manager.SelectionGraphics; 25 | } 26 | 27 | private void Start() 28 | { 29 | bottomLeftPrevPosition = selectionBottomLeft.position; 30 | topRightPrevPosition = selectionTopRight.position; 31 | 32 | Synchronize( selectionBottomLeft.position, selectionTopRight.position ); 33 | } 34 | 35 | public void Synchronize() 36 | { 37 | Vector2 bottomLeftPosition = selectionBottomLeft.position; 38 | Vector2 topRightPosition = selectionTopRight.position; 39 | 40 | if( bottomLeftPosition != bottomLeftPrevPosition || topRightPosition != topRightPrevPosition ) 41 | Synchronize( bottomLeftPosition, topRightPosition ); 42 | } 43 | 44 | private void Synchronize( Vector2 bottomLeft, Vector2 topRight ) 45 | { 46 | Vector2 position = viewport.InverseTransformPoint( bottomLeft ); 47 | Vector2 size = (Vector2) viewport.InverseTransformPoint( topRight ) - position; 48 | 49 | selectionGraphics.anchoredPosition = position; 50 | selectionGraphics.sizeDelta = size; 51 | 52 | bottomLeftPrevPosition = bottomLeft; 53 | topRightPrevPosition = topRight; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/FadeOverlayGraphic.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | 4 | namespace ImageCropperNamespace 5 | { 6 | [RequireComponent( typeof( CanvasRenderer ) )] 7 | public class FadeOverlayGraphic : Graphic 8 | { 9 | private const float OFFSET = 20000f; 10 | 11 | [SerializeField] 12 | private Sprite renderSprite; 13 | 14 | private Vector2 uv = Vector2.zero; 15 | private Color32 color32; 16 | 17 | public override Texture mainTexture { get { return renderSprite != null ? renderSprite.texture : s_WhiteTexture; } } 18 | 19 | protected override void Awake() 20 | { 21 | base.Awake(); 22 | 23 | if( renderSprite != null ) 24 | { 25 | Vector4 packedUv = UnityEngine.Sprites.DataUtility.GetOuterUV( renderSprite ); 26 | uv = new Vector2( packedUv.x + packedUv.z, packedUv.y + packedUv.w ) * 0.5f; // uv center point 27 | } 28 | } 29 | 30 | protected override void OnPopulateMesh( VertexHelper vh ) 31 | { 32 | Rect r = GetPixelAdjustedRect(); 33 | 34 | float xMin = r.x, xMax = r.x + r.width; 35 | float yMin = r.y, yMax = r.y + r.height; 36 | 37 | color32 = color; 38 | vh.Clear(); 39 | 40 | GenerateMesh( vh, -OFFSET, yMax, OFFSET, OFFSET, 0 ); 41 | GenerateMesh( vh, -OFFSET, -OFFSET, OFFSET, yMin, 4 ); 42 | GenerateMesh( vh, -OFFSET, yMin, xMin, yMax, 8 ); 43 | GenerateMesh( vh, xMax, yMin, OFFSET, yMax, 12 ); 44 | } 45 | 46 | private void GenerateMesh( VertexHelper vh, float xMin, float yMin, float xMax, float yMax, int triangleIndex ) 47 | { 48 | vh.AddVert( new Vector3( xMin, yMin ), color32, uv ); 49 | vh.AddVert( new Vector3( xMin, yMax ), color32, uv ); 50 | vh.AddVert( new Vector3( xMax, yMax ), color32, uv ); 51 | vh.AddVert( new Vector3( xMax, yMin ), color32, uv ); 52 | 53 | vh.AddTriangle( triangleIndex, triangleIndex + 1, triangleIndex + 2 ); 54 | vh.AddTriangle( triangleIndex + 2, triangleIndex + 3, triangleIndex ); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionCornersFitter.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace ImageCropperNamespace 4 | { 5 | public class SelectionCornersFitter : MonoBehaviour 6 | { 7 | [SerializeField] 8 | private RectTransform selection; 9 | 10 | [SerializeField] 11 | private RectTransform bottomLeft; 12 | 13 | [SerializeField] 14 | private RectTransform bottomRight; 15 | 16 | [SerializeField] 17 | private RectTransform topLeft; 18 | 19 | [SerializeField] 20 | private RectTransform topRight; 21 | 22 | [SerializeField] 23 | private float preferredCornerSize = 30f; 24 | 25 | [SerializeField] 26 | private float cornerSizeMaxRatio = 0.3f; 27 | 28 | private Vector2 inset; 29 | 30 | private void OnEnable() 31 | { 32 | inset = ( (RectTransform) transform ).sizeDelta * 0.5f; 33 | OnRectTransformDimensionsChange(); 34 | } 35 | 36 | private void OnRectTransformDimensionsChange() 37 | { 38 | if( !gameObject.activeInHierarchy ) 39 | return; 40 | 41 | Vector2 cornerSize; 42 | Vector2 maxCornerSize = selection.rect.size * cornerSizeMaxRatio + inset; 43 | if( preferredCornerSize <= maxCornerSize.x && preferredCornerSize <= maxCornerSize.y ) 44 | cornerSize = new Vector2( preferredCornerSize, preferredCornerSize ); 45 | else 46 | cornerSize = Vector2.one * Mathf.Min( maxCornerSize.x, maxCornerSize.y ); 47 | 48 | float halfCornerSize = cornerSize.x * 0.5f; 49 | 50 | bottomLeft.anchoredPosition = new Vector2( halfCornerSize, halfCornerSize ); 51 | bottomLeft.sizeDelta = cornerSize; 52 | 53 | bottomRight.anchoredPosition = new Vector2( -halfCornerSize, halfCornerSize ); 54 | bottomRight.sizeDelta = cornerSize; 55 | 56 | topLeft.anchoredPosition = new Vector2( halfCornerSize, -halfCornerSize ); 57 | topLeft.sizeDelta = cornerSize; 58 | 59 | topRight.anchoredPosition = new Vector2( -halfCornerSize, -halfCornerSize ); 60 | topRight.sizeDelta = cornerSize; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/EventSystemHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.EventSystems; 3 | using UnityEngine.SceneManagement; 4 | #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER 5 | using UnityEngine.InputSystem.UI; 6 | #endif 7 | 8 | namespace ImageCropperNamespace 9 | { 10 | // Avoid multiple EventSystems in the scene by activating the embedded EventSystem only if one doesn't already exist in the scene 11 | [DefaultExecutionOrder( 1000 )] 12 | public class EventSystemHandler : MonoBehaviour 13 | { 14 | [SerializeField] 15 | private GameObject embeddedEventSystem; 16 | 17 | #if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER 18 | private void Awake() 19 | { 20 | StandaloneInputModule legacyInputModule = embeddedEventSystem.GetComponent(); 21 | if( legacyInputModule ) 22 | { 23 | DestroyImmediate( legacyInputModule ); 24 | embeddedEventSystem.AddComponent(); 25 | } 26 | } 27 | #endif 28 | 29 | private void OnEnable() 30 | { 31 | SceneManager.sceneLoaded -= OnSceneLoaded; 32 | SceneManager.sceneLoaded += OnSceneLoaded; 33 | SceneManager.sceneUnloaded -= OnSceneUnloaded; 34 | SceneManager.sceneUnloaded += OnSceneUnloaded; 35 | 36 | ActivateEventSystemIfNeeded(); 37 | } 38 | 39 | private void OnDisable() 40 | { 41 | SceneManager.sceneLoaded -= OnSceneLoaded; 42 | SceneManager.sceneUnloaded -= OnSceneUnloaded; 43 | 44 | DeactivateEventSystem(); 45 | } 46 | 47 | private void OnSceneLoaded( Scene scene, LoadSceneMode mode ) 48 | { 49 | DeactivateEventSystem(); 50 | ActivateEventSystemIfNeeded(); 51 | } 52 | 53 | private void OnSceneUnloaded( Scene current ) 54 | { 55 | // Deactivate the embedded EventSystem before changing scenes because the new scene might have its own EventSystem 56 | DeactivateEventSystem(); 57 | } 58 | 59 | private void ActivateEventSystemIfNeeded() 60 | { 61 | if( embeddedEventSystem && !EventSystem.current ) 62 | embeddedEventSystem.SetActive( true ); 63 | } 64 | 65 | private void DeactivateEventSystem() 66 | { 67 | if( embeddedEventSystem ) 68 | embeddedEventSystem.SetActive( false ); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/NotchCompensator.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | #if UNITY_EDITOR 4 | using Screen = UnityEngine.Device.Screen; // To support Device Simulator on Unity 2021.1+ 5 | #endif 6 | 7 | namespace ImageCropperNamespace 8 | { 9 | public class NotchCompensator : MonoBehaviour 10 | { 11 | #if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS 12 | [SerializeField] 13 | [Tooltip( "If enabled, on Android and iOS devices with notch screens, top buttons will be repositioned so that the cutout(s) don't obscure them" )] 14 | private bool avoidScreenCutout = true; 15 | 16 | [SerializeField] 17 | private RectTransform buttons; 18 | [SerializeField] 19 | private RectTransform viewport; 20 | [SerializeField] 21 | private Image notchBackground; 22 | 23 | private RectTransform canvasTR; 24 | 25 | private bool screenDimensionsChanged = true; 26 | 27 | private void Awake() 28 | { 29 | canvasTR = (RectTransform) transform; 30 | } 31 | 32 | // Window is resized, update the list 33 | private void OnRectTransformDimensionsChange() 34 | { 35 | screenDimensionsChanged = true; 36 | } 37 | 38 | private void LateUpdate() 39 | { 40 | if( screenDimensionsChanged ) 41 | { 42 | CheckScreenCutout(); 43 | screenDimensionsChanged = false; 44 | } 45 | } 46 | 47 | // If a cutout is intersecting with the buttons at the top on notch screens, shift these buttons downwards 48 | private void CheckScreenCutout() 49 | { 50 | if( !avoidScreenCutout ) 51 | return; 52 | 53 | // Check if there is a cutout at the top of the screen 54 | int screenHeight = Screen.height; 55 | float safeYMax = Screen.safeArea.yMax; 56 | if( safeYMax < screenHeight - 1 ) // 1: a small threshold 57 | { 58 | // There is a cutout, shift the top buttons downwards 59 | float cutoutPercentage = ( screenHeight - safeYMax ) / Screen.height; 60 | float cutoutLocalSize = cutoutPercentage * canvasTR.rect.height; 61 | 62 | buttons.anchoredPosition = new Vector2( 0f, -cutoutLocalSize ); 63 | viewport.sizeDelta = new Vector2( 0f, -cutoutLocalSize ); 64 | notchBackground.rectTransform.sizeDelta = new Vector2( 0f, cutoutLocalSize + 5f ); // 5f: to prevent a thin black line from appearing when canvas is scaled with screen size 65 | 66 | if( !notchBackground.enabled ) 67 | notchBackground.enabled = true; 68 | } 69 | else 70 | { 71 | buttons.anchoredPosition = Vector2.zero; 72 | viewport.sizeDelta = Vector2.zero; 73 | 74 | if( notchBackground.enabled ) 75 | notchBackground.enabled = false; 76 | } 77 | } 78 | #endif 79 | } 80 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ImageCropperUtils.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using Orientation = ImageCropper.Orientation; 3 | 4 | namespace ImageCropperNamespace 5 | { 6 | public static class ImageCropperUtils 7 | { 8 | public static Vector2 RoundToInt( this Vector2 vector ) 9 | { 10 | vector.x = (int) ( vector.x + 0.5f ); 11 | vector.y = (int) ( vector.y + 0.5f ); 12 | 13 | return vector; 14 | } 15 | 16 | public static Vector2 CeilToInt( this Vector2 vector ) 17 | { 18 | vector.x = (int) ( vector.x + 0.999f ); 19 | vector.y = (int) ( vector.y + 0.999f ); 20 | 21 | return vector; 22 | } 23 | 24 | public static Vector2 FloorToInt( this Vector2 vector ) 25 | { 26 | vector.x = (int) vector.x; 27 | vector.y = (int) vector.y; 28 | 29 | return vector; 30 | } 31 | 32 | public static Vector2 ClampBetween( this Vector2 vector, Vector2 min, Vector2 max ) 33 | { 34 | if( min.x < max.x ) 35 | { 36 | if( vector.x < min.x ) 37 | vector.x = min.x; 38 | else if( vector.x > max.x ) 39 | vector.x = max.x; 40 | } 41 | else 42 | { 43 | if( vector.x < max.x ) 44 | vector.x = max.x; 45 | else if( vector.x > min.x ) 46 | vector.x = min.x; 47 | } 48 | 49 | if( min.y < max.y ) 50 | { 51 | if( vector.y < min.y ) 52 | vector.y = min.y; 53 | else if( vector.y > max.y ) 54 | vector.y = max.y; 55 | } 56 | else 57 | { 58 | if( vector.y < max.y ) 59 | vector.y = max.y; 60 | else if( vector.y > min.y ) 61 | vector.y = min.y; 62 | } 63 | 64 | return vector; 65 | } 66 | 67 | public static Vector2 LerpTo( this Vector2 from, Vector2 to, float t ) 68 | { 69 | return new Vector2( from.x + ( to.x - from.x ) * t, from.y + ( to.y - from.y ) * t ); 70 | } 71 | 72 | public static Vector2 ScaleWith( this Vector2 vector, Vector2 scale ) 73 | { 74 | return new Vector2( vector.x * scale.x, vector.y * scale.y ); 75 | } 76 | 77 | public static Orientation GetExifFixOrientation( this Orientation exifOrientation ) 78 | { 79 | if( exifOrientation == Orientation.Normal ) 80 | return Orientation.Normal; 81 | else if( exifOrientation == Orientation.Rotate90 ) 82 | return Orientation.Rotate270; 83 | else if( exifOrientation == Orientation.Rotate180 ) 84 | return Orientation.Rotate180; 85 | else if( exifOrientation == Orientation.Rotate270 ) 86 | return Orientation.Rotate90; 87 | else if( exifOrientation == Orientation.FlipHorizontal ) 88 | return Orientation.FlipHorizontal; 89 | else if( exifOrientation == Orientation.Transpose ) 90 | return Orientation.Transverse; 91 | else if( exifOrientation == Orientation.FlipVertical ) 92 | return Orientation.FlipVertical; 93 | else 94 | return Orientation.Transpose; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Demo/ImageCropperDemo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | using UnityEngine.UI; 4 | 5 | namespace ImageCropperNamespace 6 | { 7 | public class ImageCropperDemo : MonoBehaviour 8 | { 9 | public RawImage croppedImageHolder; 10 | public Text croppedImageSize; 11 | 12 | public Toggle ovalSelectionInput, autoZoomInput; 13 | public InputField minAspectRatioInput, maxAspectRatioInput; 14 | 15 | public void Crop() 16 | { 17 | // If image cropper is already open, do nothing 18 | if( ImageCropper.Instance.IsOpen ) 19 | return; 20 | 21 | StartCoroutine( TakeScreenshotAndCrop() ); 22 | } 23 | 24 | private IEnumerator TakeScreenshotAndCrop() 25 | { 26 | yield return new WaitForEndOfFrame(); 27 | 28 | bool ovalSelection = ovalSelectionInput.isOn; 29 | bool autoZoom = autoZoomInput.isOn; 30 | 31 | float minAspectRatio, maxAspectRatio; 32 | if( !float.TryParse( minAspectRatioInput.text, out minAspectRatio ) ) 33 | minAspectRatio = 0f; 34 | if( !float.TryParse( maxAspectRatioInput.text, out maxAspectRatio ) ) 35 | maxAspectRatio = 0f; 36 | 37 | Texture2D screenshot = new Texture2D( Screen.width, Screen.height, TextureFormat.RGB24, false ); 38 | screenshot.ReadPixels( new Rect( 0, 0, Screen.width, Screen.height ), 0, 0 ); 39 | screenshot.Apply(); 40 | 41 | ImageCropper.Instance.Show( screenshot, ( bool result, Texture originalImage, Texture2D croppedImage ) => 42 | { 43 | // Destroy previously cropped texture (if any) to free memory 44 | Destroy( croppedImageHolder.texture, 5f ); 45 | 46 | // If screenshot was cropped successfully 47 | if( result ) 48 | { 49 | // Assign cropped texture to the RawImage 50 | croppedImageHolder.enabled = true; 51 | croppedImageHolder.texture = croppedImage; 52 | 53 | Vector2 size = croppedImageHolder.rectTransform.sizeDelta; 54 | if( croppedImage.height <= croppedImage.width ) 55 | size = new Vector2( 400f, 400f * ( croppedImage.height / (float) croppedImage.width ) ); 56 | else 57 | size = new Vector2( 400f * ( croppedImage.width / (float) croppedImage.height ), 400f ); 58 | croppedImageHolder.rectTransform.sizeDelta = size; 59 | 60 | croppedImageSize.enabled = true; 61 | croppedImageSize.text = "Image size: " + croppedImage.width + ", " + croppedImage.height; 62 | } 63 | else 64 | { 65 | croppedImageHolder.enabled = false; 66 | croppedImageSize.enabled = false; 67 | } 68 | 69 | // Destroy the screenshot as we no longer need it in this case 70 | Destroy( screenshot ); 71 | }, 72 | settings: new ImageCropper.Settings() 73 | { 74 | ovalSelection = ovalSelection, 75 | autoZoomEnabled = autoZoom, 76 | imageBackground = Color.clear, // transparent background 77 | selectionMinAspectRatio = minAspectRatio, 78 | selectionMaxAspectRatio = maxAspectRatio 79 | 80 | }, 81 | croppedImageResizePolicy: ( ref int width, ref int height ) => 82 | { 83 | // uncomment lines below to save cropped image at half resolution 84 | //width /= 2; 85 | //height /= 2; 86 | } ); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionMovementHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.EventSystems; 3 | using Direction = ImageCropper.Direction; 4 | 5 | namespace ImageCropperNamespace 6 | { 7 | public class SelectionMovementHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, ISelectionHandler 8 | { 9 | private const float SCROLL_DISTANCE = 5f; 10 | 11 | [SerializeField] 12 | private ImageCropper manager; 13 | 14 | private RectTransform selection; 15 | 16 | private Vector2 initialPosition; 17 | private Vector2 initialTouchPosition; 18 | 19 | private int draggingPointer; 20 | 21 | private void Awake() 22 | { 23 | selection = manager.Selection; 24 | } 25 | 26 | private void OnDisable() 27 | { 28 | manager.StopModifySelectionWith( this ); 29 | } 30 | 31 | public void OnBeginDrag( PointerEventData eventData ) 32 | { 33 | if( !manager.CanModifySelectionWith( this ) ) 34 | { 35 | eventData.pointerDrag = null; 36 | return; 37 | } 38 | 39 | draggingPointer = eventData.pointerId; 40 | 41 | initialPosition = selection.anchoredPosition; 42 | initialTouchPosition = manager.GetTouchPosition( eventData.pressPosition, eventData.pressEventCamera ); 43 | } 44 | 45 | public void OnDrag( PointerEventData eventData ) 46 | { 47 | if( eventData.pointerId != draggingPointer ) 48 | { 49 | eventData.pointerDrag = null; 50 | return; 51 | } 52 | 53 | manager.UpdateSelection( initialPosition + manager.GetTouchPosition( eventData.position, eventData.pressEventCamera ) - initialTouchPosition ); 54 | } 55 | 56 | public void OnEndDrag( PointerEventData eventData ) 57 | { 58 | if( eventData.pointerId == draggingPointer ) 59 | manager.StopModifySelectionWith( this ); 60 | } 61 | 62 | public void OnUpdate() 63 | { 64 | bool shouldUpdateViewport = false; 65 | float scale = manager.ImageHolder.localScale.z; 66 | 67 | Vector2 imagePosition = manager.ImageHolder.anchoredPosition; 68 | Vector2 selectionBottomLeft = imagePosition + selection.anchoredPosition * scale; 69 | Vector2 selectionTopRight = selectionBottomLeft + selection.sizeDelta * scale; 70 | Vector2 selectionSize = selectionTopRight - selectionBottomLeft; 71 | 72 | Vector2 viewportSize = manager.ViewportSize; 73 | 74 | if( selectionBottomLeft.x <= SCROLL_DISTANCE ) 75 | { 76 | imagePosition = manager.ScrollImage( imagePosition, Direction.Left ); 77 | selectionBottomLeft.x = 0f; 78 | 79 | shouldUpdateViewport = true; 80 | } 81 | else if( selectionTopRight.x >= viewportSize.x - SCROLL_DISTANCE ) 82 | { 83 | imagePosition = manager.ScrollImage( imagePosition, Direction.Right ); 84 | selectionBottomLeft.x = viewportSize.x - selectionSize.x; 85 | 86 | shouldUpdateViewport = true; 87 | } 88 | 89 | if( selectionBottomLeft.y <= SCROLL_DISTANCE ) 90 | { 91 | imagePosition = manager.ScrollImage( imagePosition, Direction.Bottom ); 92 | selectionBottomLeft.y = 0f; 93 | 94 | shouldUpdateViewport = true; 95 | } 96 | else if( selectionTopRight.y >= viewportSize.y - SCROLL_DISTANCE ) 97 | { 98 | imagePosition = manager.ScrollImage( imagePosition, Direction.Top ); 99 | selectionBottomLeft.y = viewportSize.y - selectionSize.y; 100 | 101 | shouldUpdateViewport = true; 102 | } 103 | 104 | if( shouldUpdateViewport ) 105 | { 106 | manager.ImageHolder.anchoredPosition = imagePosition; 107 | manager.UpdateSelection( ( selectionBottomLeft - imagePosition ) / scale ); 108 | } 109 | } 110 | 111 | public void Stop() 112 | { 113 | draggingPointer--; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBg.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bea2c3bc476233040b1fee52ad90f372 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/FlipHorizontalButton.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7a0e0abeb9a1e1842b96697e569c4a10 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/FlipVerticalButton.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fe2779cd5f5f9f4aa47fbb2b2df7c77 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBorders.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e2f2c6559fc415b49a19aaa8bf684dc0 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 3, y: 3, z: 3, w: 3} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperButtonsBg.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb7ac2c4a1893b24f84696c7e052c463 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 7, y: 8, z: 7, w: 8} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperCorners.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0be23f5c35fdf564ab35c16f6ad96f43 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 6, y: 6, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/RotateImageButton.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0f3996b413bd4c44b9e5d18e99bc7e9 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Sprites/ImageCropperBordersThick.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 76197f564ff144b4abdc47609e110582 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 16 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 5, y: 5, z: 5, w: 5} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 1 66 | cookieLightType: 1 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 256 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 1 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 256 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 1 92 | - serializedVersion: 3 93 | buildTarget: Android 94 | maxTextureSize: 256 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 1 104 | - serializedVersion: 3 105 | buildTarget: WebGL 106 | maxTextureSize: 256 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 1 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 5e97eb03825dee720800000000000000 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: ImageCropper 131 | pSDRemoveMatte: 1 132 | pSDShowRemoveMatteOption: 1 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/CircleGraphic.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | 4 | namespace ImageCropperNamespace 5 | { 6 | [RequireComponent( typeof( CanvasRenderer ) )] 7 | public class CircleGraphic : MaskableGraphic 8 | { 9 | public enum Mode { FillInside = 0, FillOutside = 1, Edge = 2 }; 10 | 11 | [SerializeField] 12 | private Sprite renderSprite; 13 | 14 | [SerializeField] 15 | private int detail = 64; 16 | 17 | [SerializeField] 18 | private Mode mode; 19 | 20 | [SerializeField] 21 | private float edgeThickness = 1; 22 | 23 | private Vector2 uv; 24 | private Color32 color32; 25 | 26 | private float width, height; 27 | private float deltaWidth, deltaHeight; 28 | private float deltaRadians; 29 | 30 | public override Texture mainTexture { get { return renderSprite != null ? renderSprite.texture : s_WhiteTexture; } } 31 | 32 | protected override void Awake() 33 | { 34 | base.Awake(); 35 | 36 | if( renderSprite != null ) 37 | { 38 | Vector4 packedUv = UnityEngine.Sprites.DataUtility.GetOuterUV( renderSprite ); 39 | uv = new Vector2( packedUv.x + packedUv.z, packedUv.y + packedUv.w ) * 0.5f; // uv center point 40 | } 41 | } 42 | 43 | protected override void OnPopulateMesh( VertexHelper vh ) 44 | { 45 | Rect r = GetPixelAdjustedRect(); 46 | 47 | color32 = color; 48 | width = r.width * 0.5f; 49 | height = r.height * 0.5f; 50 | 51 | vh.Clear(); 52 | 53 | Vector2 pivot = rectTransform.pivot; 54 | deltaWidth = r.width * ( 0.5f - pivot.x ); 55 | deltaHeight = r.height * ( 0.5f - pivot.y ); 56 | 57 | if( mode == Mode.FillInside ) 58 | { 59 | deltaRadians = 360f / detail * Mathf.Deg2Rad; 60 | FillInside( vh ); 61 | } 62 | else if( mode == Mode.FillOutside ) 63 | { 64 | int quarterDetail = ( detail + 3 ) / 4; 65 | deltaRadians = 360f / ( quarterDetail * 4 ) * Mathf.Deg2Rad; 66 | 67 | vh.AddVert( new Vector3( width + deltaWidth, height + deltaHeight, 0f ), color32, uv ); 68 | vh.AddVert( new Vector3( -width + deltaWidth, height + deltaHeight, 0f ), color32, uv ); 69 | vh.AddVert( new Vector3( -width + deltaWidth, -height + deltaHeight, 0f ), color32, uv ); 70 | vh.AddVert( new Vector3( width + deltaWidth, -height + deltaHeight, 0f ), color32, uv ); 71 | 72 | int triangleIndex = 4; 73 | FillOutside( vh, new Vector3( width + deltaWidth, deltaHeight, 0f ), 0, quarterDetail, ref triangleIndex ); 74 | FillOutside( vh, new Vector3( deltaWidth, height + deltaHeight, 0f ), 1, quarterDetail, ref triangleIndex ); 75 | FillOutside( vh, new Vector3( -width + deltaWidth, deltaHeight, 0f ), 2, quarterDetail, ref triangleIndex ); 76 | FillOutside( vh, new Vector3( deltaWidth, -height + deltaHeight, 0f ), 3, quarterDetail, ref triangleIndex ); 77 | } 78 | else 79 | { 80 | deltaRadians = 360f / detail * Mathf.Deg2Rad; 81 | GenerateEdges( vh ); 82 | } 83 | } 84 | 85 | public override void Cull( Rect clipRect, bool validRect ) 86 | { 87 | canvasRenderer.cull = false; 88 | } 89 | 90 | private void FillInside( VertexHelper vh ) 91 | { 92 | vh.AddVert( new Vector3( deltaWidth, deltaHeight, 0f ), color32, uv ); 93 | vh.AddVert( new Vector3( width + deltaWidth, deltaHeight, 0f ), color32, uv ); 94 | 95 | int triangleIndex = 2; 96 | for( int i = 1; i < detail; i++, triangleIndex++ ) 97 | { 98 | float radians = i * deltaRadians; 99 | 100 | vh.AddVert( new Vector3( Mathf.Cos( radians ) * width + deltaWidth, Mathf.Sin( radians ) * height + deltaHeight, 0f ), color32, uv ); 101 | vh.AddTriangle( triangleIndex, triangleIndex - 1, 0 ); 102 | } 103 | 104 | vh.AddTriangle( 1, triangleIndex - 1, 0 ); 105 | } 106 | 107 | private void FillOutside( VertexHelper vh, Vector3 initialPoint, int quarterIndex, int detail, ref int triangleIndex ) 108 | { 109 | int startIndex = quarterIndex * detail; 110 | int endIndex = ( quarterIndex + 1 ) * detail; 111 | 112 | vh.AddVert( initialPoint, color32, uv ); 113 | triangleIndex++; 114 | 115 | for( int i = startIndex + 1; i <= endIndex; i++, triangleIndex++ ) 116 | { 117 | float radians = i * deltaRadians; 118 | 119 | vh.AddVert( new Vector3( Mathf.Cos( radians ) * width + deltaWidth, Mathf.Sin( radians ) * height + deltaHeight, 0f ), color32, uv ); 120 | vh.AddTriangle( quarterIndex, triangleIndex - 1, triangleIndex ); 121 | } 122 | } 123 | 124 | private void GenerateEdges( VertexHelper vh ) 125 | { 126 | float innerWidth = width - edgeThickness; 127 | float innerHeight = height - edgeThickness; 128 | 129 | vh.AddVert( new Vector3( width + deltaWidth, deltaHeight, 0f ), color32, uv ); 130 | vh.AddVert( new Vector3( innerWidth + deltaWidth, deltaHeight, 0f ), color32, uv ); 131 | 132 | int triangleIndex = 2; 133 | for( int i = 1; i < detail; i++, triangleIndex += 2 ) 134 | { 135 | float radians = i * deltaRadians; 136 | float cos = Mathf.Cos( radians ); 137 | float sin = Mathf.Sin( radians ); 138 | 139 | vh.AddVert( new Vector3( cos * width + deltaWidth, sin * height + deltaHeight, 0f ), color32, uv ); 140 | vh.AddVert( new Vector3( cos * innerWidth + deltaWidth, sin * innerHeight + deltaHeight, 0f ), color32, uv ); 141 | 142 | vh.AddTriangle( triangleIndex, triangleIndex - 2, triangleIndex - 1 ); 143 | vh.AddTriangle( triangleIndex, triangleIndex - 1, triangleIndex + 1 ); 144 | } 145 | 146 | vh.AddTriangle( 0, triangleIndex - 2, triangleIndex - 1 ); 147 | vh.AddTriangle( 0, triangleIndex - 1, 1 ); 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Unity Image Cropper 2 | 3 | ![screenshot](Images/cropper.png) 4 | 5 | **Available on Asset Store:** https://assetstore.unity.com/packages/tools/gui/image-cropper-116650 6 | 7 | **Forum Thread:** https://forum.unity.com/threads/released-image-cropper-multiplatform-image-cropping-solution-with-oval-mask-support.526901/ 8 | 9 | **Discord:** https://discord.gg/UJJt549AaV 10 | 11 | **WebGL Demo:** http://yasirkula.net/ImageCropperDemo/ 12 | 13 | **[GitHub Sponsors ☕](https://github.com/sponsors/yasirkula)** 14 | 15 | ## ABOUT 16 | 17 | ImageCropper aims to be a powerful, customizable and easy-to-use image cropping solution for Unity 3D. It is created with Unity's UI system. 18 | 19 | ## FEATURES 20 | 21 | - Supports rectangular and oval cropping modes 22 | - Supports auto-zoom feature for fine-tuning the selection 23 | - Costs 1 SetPass call and 6 batches (assuming that *Sprite Packing* is enabled in *Editor Settings*) 24 | - Can work on pretty much any screen resolution/orientation 25 | 26 | ## INSTALLATION 27 | 28 | There are 5 ways to install this plugin: 29 | 30 | - import [ImageCropper.unitypackage](https://github.com/yasirkula/UnityImageCropper/releases) via *Assets-Import Package* 31 | - clone/[download](https://github.com/yasirkula/UnityImageCropper/archive/master.zip) this repository and move the *Plugins* folder to your Unity project's *Assets* folder 32 | - import it from [Asset Store](https://assetstore.unity.com/packages/tools/gui/image-cropper-116650) 33 | - *(via Package Manager)* click the + button and install the package from the following git URL: 34 | - `https://github.com/yasirkula/UnityImageCropper.git` 35 | - *(via [OpenUPM](https://openupm.com))* after installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: 36 | - `openupm add com.yasirkula.imagecropper` 37 | 38 | ## FAQ 39 | 40 | - **New Input System isn't supported on Unity 2019.2.5 or earlier** 41 | 42 | Add `ENABLE_INPUT_SYSTEM` compiler directive to **Player Settings/Scripting Define Symbols** (these symbols are platform specific, so if you change the active platform later, you'll have to add the compiler directive again). 43 | 44 | - **"Unity.InputSystem" assembly can't be resolved on Unity 2018.4 or earlier** 45 | 46 | Remove `Unity.InputSystem` assembly from **ImageCropper.Runtime** Assembly Definition File's *Assembly Definition References* list. 47 | 48 | ## HOW TO 49 | 50 | ImageCropper is a singleton object that can be accessed via `ImageCropper.Instance`. To start cropping a *Texture* object, you can call the following function: 51 | 52 | `ImageCropper.Instance.Show( Texture image, CropResult onCrop, Settings settings = null, ImageResizePolicy croppedImageResizePolicy = null )` 53 | - **image**: the Texture object to crop 54 | - **onCrop**: callback that will be invoked after cropping is finished/cancelled. Its signature is as following: `void CropResult( bool result, Texture originalImage, Texture2D croppedImage )`. **result** stores whether the image is cropped successfully or operation is cancelled, **originalImage** stores the Texture passed as *image* parameter and **croppedImage** stores the resulting cropped image 55 | - **croppedImageResizePolicy**: called before the cropped image is saved as Texture2D. Can be used to override the size of the Texture2D object. Its signature is as following: `void ImageResizePolicy( ref int width, ref int height )`, where *width* and *height* will initially be equal to the dimensions of the selection (crop area). If left *null*, texture's size will be equal to the selection's size. Can be used to e.g. always output a 256x256 Texture2D 56 | - **settings**: can be used to adjust the parameters of the image cropper. Available parameters are: 57 | - **bool autoZoomEnabled** (*default=true*): if enabled, image cropper will zoom in to the selection (crop area) if it is too small, and zoom out if selection is too large 58 | - **bool pixelPerfectSelection** (*default=false*): if enabled, selection's position and size values will be rounded to the nearest integers. As a *RenderTexture* with a render camera is used to generate the cropped image (instead of reading the pixels of the source image), a pixel perfect selection doesn't really have a big impact on the output 59 | - **bool ovalSelection** (*default=false*): if enabled, an oval mask will be used to crop the image in oval/circular shape. Otherwise, image will be cropped in rectangular/square shape 60 | - **bool markTextureNonReadable** (*default=true*): marks the cropped texture as non-readable for better memory usage. If you plan to modify the texture later (e.g. *GetPixels/SetPixels*), set its value to *false* 61 | - **Color imageBackground** (*default=black*): determines the background color of the cropped image. Background color will be visible if source image has transparency or if *ovalSelection* is enabled. For a completely transparent background, set its value to *Color.clear* (which has 0 alpha). Note that if imageBackground is opaque (alpha=1), cropped texture will be in *RGB24* format instead of *RGBA32* format. As RGB24 uses less memory, try not to use a transparent background color unless it is needed 62 | - **Button visibleButtons** (*default=FlipHorizontal|FlipVertical|Rotate90Degrees*): determines which image orientation buttons will be visible in the user interface. By default, all buttons are visible 63 | - **Visibility guidelinesVisibility** (*default=AlwaysVisible*): determines the visibility of the selection guidelines. Accepted values are: *Hidden*, *OnDrag* (only visible while the selection is being dragged/resized) and *AlwaysVisible* 64 | - **Orientation initialOrientation** (*defaul=Normal*): initial orientation (flipped/rotated state) of the image. Please see EXIF orientations before changing its value: http://sylvana.net/jpegcrop/exif_orientation.html 65 | - **Vector2 selectionMinSize** (*default=0,0*): minimum size of the selection (crop area). If untouched, it will be equal to 1/10th of the source image's size 66 | - **Vector2 selectionMaxSize** (*default=0,0*): maximum size of the selection (crop area). If untouched, there will be no limit 67 | - **float selectionMinAspectRatio** (*default=0*): minimum aspect ratio of the selection (crop area). If untouched, there will be no limit 68 | - **float selectionMaxAspectRatio** (*default=0*): maximum aspect ratio of the selection (crop area). If untouched, there will be no limit. For a circular/square selection, you can set both *selectionMinAspectRatio* and *selectionMaxAspectRatio* to 1 69 | - **float selectionInitialPaddingLeft** (*default=0.1*): initial padding-left of the selection in % 70 | - **float selectionInitialPaddingTop** (*default=0.1*): initial padding-top of the selection in % 71 | - **float selectionInitialPaddingRight** (*default=0.1*): initial padding-right of the selection in % 72 | - **float selectionInitialPaddingBottom** (*default=0.1*): initial padding-bottom of the selection in % 73 | 74 | **NOTE:** Before calling the *Show* function, you may want to check the value of `ImageCropper.Instance.IsOpen` to make sure that the image cropper is not already visible. 75 | 76 | **NOTE:** Crop camera uses [Layer 22](https://github.com/yasirkula/UnityImageCropper/blob/14d84a729623ad980c966221a65f2cc4d7bfd496/Plugins/ImageCropper/Scripts/ImageCropper.cs#L262-L264) to render the thumbnails. On URP, make sure that that layer is included in the Layer Masks of the Forward Renderer asset. 77 | 78 | ## EXAMPLE CODE 79 | 80 | See [ImageCropperDemo.cs](../Plugins/ImageCropper/Demo/ImageCropperDemo.cs). 81 | -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/SelectionResizeHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.EventSystems; 3 | using Direction = ImageCropper.Direction; 4 | 5 | namespace ImageCropperNamespace 6 | { 7 | public class SelectionResizeHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, ISelectionHandler 8 | { 9 | private const float SCROLL_DISTANCE = 70f; 10 | private const float SELECTION_MAX_DISTANCE_FOR_SCROLL = 50f; 11 | 12 | [SerializeField] 13 | private ImageCropper manager; 14 | 15 | [SerializeField] 16 | private Direction direction; 17 | 18 | [SerializeField] 19 | private Direction secondaryDirection = Direction.None; 20 | 21 | private Direction directions; 22 | private Direction pivot; 23 | 24 | private RectTransform selection; 25 | 26 | private Vector2 initialPosition; 27 | private Vector2 initialTouchPosition; 28 | 29 | private Vector2 initialSelectionPosition; 30 | private Vector2 initialSelectionSize; 31 | 32 | private int draggingPointer; 33 | private PointerEventData draggingPointerEventData; 34 | 35 | private void Awake() 36 | { 37 | selection = manager.Selection; 38 | 39 | if( direction == Direction.None ) 40 | { 41 | Direction temp = direction; 42 | direction = secondaryDirection; 43 | secondaryDirection = temp; 44 | } 45 | 46 | directions = direction | secondaryDirection; 47 | 48 | pivot = Direction.None; 49 | if( ( directions & Direction.Left ) == Direction.Left ) 50 | pivot |= Direction.Right; 51 | else if( ( directions & Direction.Right ) == Direction.Right ) 52 | pivot |= Direction.Left; 53 | 54 | if( ( directions & Direction.Top ) == Direction.Top ) 55 | pivot |= Direction.Bottom; 56 | else if( ( directions & Direction.Bottom ) == Direction.Bottom ) 57 | pivot |= Direction.Top; 58 | } 59 | 60 | private void OnDisable() 61 | { 62 | manager.StopModifySelectionWith( this ); 63 | } 64 | 65 | public void OnBeginDrag( PointerEventData eventData ) 66 | { 67 | if( !manager.CanModifySelectionWith( this ) ) 68 | { 69 | eventData.pointerDrag = null; 70 | return; 71 | } 72 | 73 | draggingPointer = eventData.pointerId; 74 | draggingPointerEventData = eventData; 75 | 76 | if( ( directions & Direction.Left ) == Direction.Left ) 77 | initialPosition.x = selection.anchoredPosition.x; 78 | else if( ( directions & Direction.Right ) == Direction.Right ) 79 | initialPosition.x = selection.anchoredPosition.x + selection.sizeDelta.x; 80 | 81 | if( ( directions & Direction.Top ) == Direction.Top ) 82 | initialPosition.y = selection.anchoredPosition.y + selection.sizeDelta.y; 83 | else if( ( directions & Direction.Bottom ) == Direction.Bottom ) 84 | initialPosition.y = selection.anchoredPosition.y; 85 | 86 | initialTouchPosition = manager.GetTouchPosition( eventData.pressPosition, eventData.pressEventCamera ); 87 | 88 | initialSelectionPosition = selection.anchoredPosition; 89 | initialSelectionSize = selection.sizeDelta; 90 | } 91 | 92 | public void OnDrag( PointerEventData eventData ) 93 | { 94 | if( eventData.pointerId != draggingPointer ) 95 | { 96 | eventData.pointerDrag = null; 97 | return; 98 | } 99 | 100 | draggingPointerEventData = eventData; 101 | 102 | Vector2 newPosition = initialPosition + manager.GetTouchPosition( eventData.position, eventData.pressEventCamera ) - initialTouchPosition; 103 | Vector2 selectionPosition = initialSelectionPosition; 104 | Vector2 selectionSize = initialSelectionSize; 105 | 106 | if( ( directions & Direction.Left ) == Direction.Left ) 107 | { 108 | if( newPosition.x < manager.SelectionSnapToEdgeThreshold ) 109 | newPosition.x = 0f; 110 | 111 | selectionSize.x -= newPosition.x - selectionPosition.x; 112 | selectionPosition.x = newPosition.x; 113 | } 114 | else if( ( directions & Direction.Right ) == Direction.Right ) 115 | { 116 | if( newPosition.x > manager.OrientedImageSize.x - manager.SelectionSnapToEdgeThreshold ) 117 | newPosition.x = manager.OrientedImageSize.x; 118 | 119 | selectionSize.x = newPosition.x - selectionPosition.x; 120 | } 121 | 122 | if( ( directions & Direction.Top ) == Direction.Top ) 123 | { 124 | if( newPosition.y > manager.OrientedImageSize.y - manager.SelectionSnapToEdgeThreshold ) 125 | newPosition.y = manager.OrientedImageSize.y; 126 | 127 | selectionSize.y = newPosition.y - selectionPosition.y; 128 | } 129 | else if( ( directions & Direction.Bottom ) == Direction.Bottom ) 130 | { 131 | if( newPosition.y < manager.SelectionSnapToEdgeThreshold ) 132 | newPosition.y = 0f; 133 | 134 | selectionSize.y -= newPosition.y - selectionPosition.y; 135 | selectionPosition.y = newPosition.y; 136 | } 137 | 138 | bool shouldExpand = false; 139 | if( secondaryDirection == Direction.None ) 140 | { 141 | if( direction == Direction.Left || direction == Direction.Right ) 142 | { 143 | if( selectionSize.x > initialSelectionSize.x ) 144 | shouldExpand = true; 145 | } 146 | else 147 | { 148 | if( selectionSize.y > initialSelectionSize.y ) 149 | shouldExpand = true; 150 | } 151 | } 152 | 153 | manager.UpdateSelection( selectionPosition, selectionSize, pivot, !shouldExpand ); 154 | } 155 | 156 | public void OnEndDrag( PointerEventData eventData ) 157 | { 158 | if( eventData.pointerId == draggingPointer ) 159 | { 160 | draggingPointerEventData = null; 161 | manager.StopModifySelectionWith( this ); 162 | } 163 | } 164 | 165 | public void OnUpdate() 166 | { 167 | if( draggingPointerEventData == null ) 168 | return; 169 | 170 | Vector2 pointerLocalPos; 171 | RectTransformUtility.ScreenPointToLocalPointInRectangle( manager.Viewport, draggingPointerEventData.position, draggingPointerEventData.pressEventCamera, out pointerLocalPos ); 172 | 173 | bool shouldUpdateViewport = false; 174 | float scale = manager.ImageHolder.localScale.z; 175 | 176 | Vector2 imagePosition = manager.ImageHolder.anchoredPosition; 177 | Vector2 selectionBottomLeft = imagePosition + selection.anchoredPosition * scale; 178 | Vector2 selectionTopRight = selectionBottomLeft + selection.sizeDelta * scale; 179 | 180 | Vector2 viewportSize = manager.ViewportSize; 181 | 182 | if( ( directions & Direction.Left ) == Direction.Left || ( directions & Direction.Right ) == Direction.Right ) 183 | { 184 | if( pointerLocalPos.x <= SCROLL_DISTANCE && selectionBottomLeft.x <= SELECTION_MAX_DISTANCE_FOR_SCROLL ) 185 | { 186 | imagePosition = manager.ScrollImage( imagePosition, Direction.Left ); 187 | shouldUpdateViewport = true; 188 | } 189 | else if( pointerLocalPos.x >= viewportSize.x - SCROLL_DISTANCE && selectionTopRight.x >= viewportSize.x - SELECTION_MAX_DISTANCE_FOR_SCROLL ) 190 | { 191 | imagePosition = manager.ScrollImage( imagePosition, Direction.Right ); 192 | shouldUpdateViewport = true; 193 | } 194 | } 195 | 196 | if( ( directions & Direction.Bottom ) == Direction.Bottom || ( directions & Direction.Top ) == Direction.Top ) 197 | { 198 | if( pointerLocalPos.y <= SCROLL_DISTANCE && selectionBottomLeft.y <= SELECTION_MAX_DISTANCE_FOR_SCROLL ) 199 | { 200 | imagePosition = manager.ScrollImage( imagePosition, Direction.Bottom ); 201 | shouldUpdateViewport = true; 202 | } 203 | else if( pointerLocalPos.y >= viewportSize.y - SCROLL_DISTANCE && selectionTopRight.y >= viewportSize.y - SELECTION_MAX_DISTANCE_FOR_SCROLL ) 204 | { 205 | imagePosition = manager.ScrollImage( imagePosition, Direction.Top ); 206 | shouldUpdateViewport = true; 207 | } 208 | } 209 | 210 | if( shouldUpdateViewport ) 211 | { 212 | manager.ImageHolder.anchoredPosition = imagePosition; 213 | OnDrag( draggingPointerEventData ); 214 | } 215 | } 216 | 217 | public void Stop() 218 | { 219 | draggingPointer--; 220 | draggingPointerEventData = null; 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /Plugins/ImageCropper/Scripts/ImageCropper.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | using ImageCropperNamespace; 4 | using System.Collections; 5 | 6 | // === 7 | // Auto-zoom feature inspired from: Android Image Cropper https://github.com/ArthurHub/Android-Image-Cropper 8 | // Copyright 2016, Arthur Teplitzki, 2013, Edmodo, Inc. 9 | // License (Apache License 2.0) https://github.com/ArthurHub/Android-Image-Cropper/blob/master/LICENSE.txt 10 | // === 11 | 12 | public class ImageCropper : MonoBehaviour 13 | { 14 | public class Settings 15 | { 16 | public bool autoZoomEnabled = true; 17 | public bool pixelPerfectSelection = false; 18 | public bool ovalSelection = false; 19 | public bool markTextureNonReadable = true; 20 | 21 | public Color imageBackground = Color.black; 22 | 23 | public Button visibleButtons = ~Button.None; 24 | public Visibility guidelinesVisibility = Visibility.AlwaysVisible; 25 | public Orientation initialOrientation = Orientation.Normal; 26 | 27 | public Vector2 selectionMinSize = Vector2.zero; 28 | public Vector2 selectionMaxSize = Vector2.zero; 29 | 30 | public float selectionMinAspectRatio = 0f; 31 | public float selectionMaxAspectRatio = 0f; 32 | 33 | public float selectionInitialPaddingLeft = 0.1f; 34 | public float selectionInitialPaddingTop = 0.1f; 35 | public float selectionInitialPaddingRight = 0.1f; 36 | public float selectionInitialPaddingBottom = 0.1f; 37 | } 38 | 39 | [System.Flags] 40 | public enum Direction { None = 0, Left = 1, Top = 2, Right = 4, Bottom = 8 }; 41 | 42 | [System.Flags] 43 | public enum Button { None = 0, FlipHorizontal = 1, FlipVertical = 2, Rotate90Degrees = 4 }; 44 | 45 | public enum Visibility { Hidden = 0, OnDrag = 1, AlwaysVisible = 2 }; 46 | 47 | // EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered) 48 | public enum Orientation { Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 }; 49 | 50 | public delegate void CropResult( bool result, Texture originalImage, Texture2D croppedImage ); 51 | public delegate void ImageResizePolicy( ref int width, ref int height ); 52 | 53 | private static ImageCropper m_instance = null; 54 | public static ImageCropper Instance 55 | { 56 | get 57 | { 58 | if( m_instance == null ) 59 | m_instance = Instantiate( Resources.Load( "ImageCropper" ) ); 60 | 61 | return m_instance; 62 | } 63 | } 64 | 65 | [Header( "Properties" )] 66 | [SerializeField] 67 | private float autoZoomInThreshold = 0.5f; 68 | 69 | [SerializeField] 70 | private float autoZoomOutThreshold = 0.65f; 71 | 72 | [SerializeField] 73 | private float autoZoomInFillAmount = 0.64f; 74 | 75 | [SerializeField] 76 | private float autoZoomOutFillAmount = 0.51f; 77 | 78 | [SerializeField] 79 | private AnimationCurve autoZoomCurve; 80 | 81 | [SerializeField] 82 | private float m_selectionSnapToEdgeThreshold = 5f; 83 | public float SelectionSnapToEdgeThreshold { get { return m_selectionSnapToEdgeThreshold; } } 84 | 85 | [SerializeField] 86 | private float viewportScrollSpeed = 512f; 87 | 88 | [Header( "Internal Variables" )] 89 | [SerializeField] 90 | private Canvas canvas; 91 | 92 | [SerializeField] 93 | private RectTransform m_viewport; 94 | public RectTransform Viewport { get { return m_viewport; } } 95 | 96 | [SerializeField] 97 | private RectTransform m_imageHolder; 98 | public RectTransform ImageHolder { get { return m_imageHolder; } } 99 | 100 | [SerializeField] 101 | private RawImage m_orientedImage; 102 | public RawImage OrientedImage { get { return m_orientedImage; } } 103 | 104 | [SerializeField] 105 | private RectTransform m_selection; 106 | public RectTransform Selection { get { return m_selection; } } 107 | 108 | [SerializeField] 109 | private RectTransform m_selectionGraphics; 110 | public RectTransform SelectionGraphics { get { return m_selectionGraphics; } } 111 | 112 | [SerializeField] 113 | private SizeChangeListener viewportSizeChangeListener; 114 | 115 | [SerializeField] 116 | private SelectionGraphicsSynchronizer selectionGraphicsSynchronizer; 117 | 118 | [SerializeField] 119 | private Behaviour[] ovalMaskElements; 120 | 121 | [SerializeField] 122 | private Behaviour[] guidelines; 123 | 124 | [Header( "User Interface" )] 125 | [SerializeField] 126 | private GameObject flipHorizontalButton; 127 | 128 | [SerializeField] 129 | private GameObject flipVerticalButton; 130 | 131 | [SerializeField] 132 | private GameObject rotate90DegreesButton; 133 | 134 | [SerializeField] 135 | private FontSizeSynchronizer textsSynchronizer; 136 | 137 | [Header( "Crop Helpers" )] 138 | [SerializeField] 139 | private Canvas cropRenderCanvas; 140 | 141 | [SerializeField] 142 | private RawImage cropRenderImage; 143 | 144 | [SerializeField] 145 | private RectTransform cropRenderSelection; 146 | 147 | [SerializeField] 148 | private Camera cropRenderCamera; 149 | 150 | public bool IsOpen { get { return gameObject.activeSelf; } } 151 | 152 | private Settings m_defaultSettings; 153 | private Settings DefaultSettings 154 | { 155 | get 156 | { 157 | if( m_defaultSettings == null ) 158 | m_defaultSettings = new Settings(); 159 | 160 | return m_defaultSettings; 161 | } 162 | } 163 | 164 | private bool m_autoZoomEnabled; 165 | public bool AutoZoomEnabled 166 | { 167 | get { return m_autoZoomEnabled; } 168 | set 169 | { 170 | m_autoZoomEnabled = value; 171 | if( m_autoZoomEnabled ) 172 | StartAutoZoom( false ); 173 | } 174 | } 175 | 176 | private bool m_pixelPerfectSelection; 177 | public bool PixelPerfectSelection 178 | { 179 | get { return m_pixelPerfectSelection; } 180 | set 181 | { 182 | m_pixelPerfectSelection = value; 183 | if( m_pixelPerfectSelection ) 184 | MakePixelPerfectSelection(); 185 | } 186 | } 187 | 188 | private bool m_ovalSelection; 189 | public bool OvalSelection 190 | { 191 | get { return m_ovalSelection; } 192 | set 193 | { 194 | m_ovalSelection = value; 195 | for( int i = 0; i < ovalMaskElements.Length; i++ ) 196 | ovalMaskElements[i].enabled = m_ovalSelection; 197 | } 198 | } 199 | 200 | private Visibility m_guidelinesVisibility; 201 | public Visibility GuidelinesVisibility 202 | { 203 | get { return m_guidelinesVisibility; } 204 | set 205 | { 206 | m_guidelinesVisibility = value; 207 | 208 | bool visible = m_guidelinesVisibility == Visibility.AlwaysVisible; 209 | for( int i = 0; i < guidelines.Length; i++ ) 210 | guidelines[i].enabled = visible; 211 | } 212 | } 213 | 214 | public bool MarkTextureNonReadable { get; set; } 215 | 216 | public Color ImageBackground { get; set; } 217 | 218 | private Vector2 m_viewportSize; 219 | public Vector2 ViewportSize { get { return m_viewportSize; } } 220 | 221 | private Vector2 m_originalImageSize; 222 | public Vector2 OriginalImageSize { get { return m_originalImageSize; } } 223 | 224 | private Vector2 m_orientedImageSize; 225 | public Vector2 OrientedImageSize { get { return m_orientedImageSize; } } 226 | 227 | public Vector2 SelectionSize { get { return m_selection.sizeDelta; } } 228 | 229 | private RectTransform orientedImageTransform; 230 | 231 | private IEnumerator autoZoomCoroutine; 232 | private ISelectionHandler currentSelectionHandler; 233 | 234 | private Orientation currentOrientation; 235 | 236 | private CropResult cropCallback; 237 | private ImageResizePolicy imageResizePolicy; 238 | 239 | private bool shouldRefreshViewport; 240 | 241 | private Vector2 minSize, maxSize; 242 | private Vector2 currMinSize, currMaxSize; 243 | 244 | private float minAspectRatio, maxAspectRatio; 245 | private float minImageScale; 246 | 247 | private void Awake() 248 | { 249 | if( m_instance == null ) 250 | m_instance = this; 251 | else if( this != m_instance ) 252 | { 253 | Destroy( gameObject ); 254 | return; 255 | } 256 | 257 | orientedImageTransform = (RectTransform) m_orientedImage.transform; 258 | viewportSizeChangeListener.onSizeChanged = OnViewportDimensionsChange; 259 | 260 | cropRenderCanvas.gameObject.layer = 22; // some random layer that is hopefully not used by any other object 261 | cropRenderImage.gameObject.layer = 22; 262 | cropRenderCamera.cullingMask = 1 << 22; 263 | cropRenderCanvas.gameObject.SetActive( false ); 264 | 265 | gameObject.SetActive( false ); 266 | } 267 | 268 | private void OnDisable() 269 | { 270 | autoZoomCoroutine = null; 271 | } 272 | 273 | private void LateUpdate() 274 | { 275 | if( gameObject.activeInHierarchy ) 276 | { 277 | if( currentSelectionHandler != null && m_imageHolder.localScale.z > minImageScale + 0.01f ) 278 | currentSelectionHandler.OnUpdate(); 279 | 280 | if( shouldRefreshViewport ) 281 | { 282 | textsSynchronizer.Synchronize(); 283 | ResetView( true ); 284 | 285 | shouldRefreshViewport = false; 286 | } 287 | 288 | selectionGraphicsSynchronizer.Synchronize(); 289 | } 290 | } 291 | 292 | private void OnViewportDimensionsChange( Vector2 size ) 293 | { 294 | m_viewportSize = size; 295 | shouldRefreshViewport = true; 296 | } 297 | 298 | public void Show( Texture image, Settings settings = null ) 299 | { 300 | Show( image, null, settings, null ); 301 | } 302 | 303 | public void Show( Texture image, CropResult onCrop, Settings settings = null, ImageResizePolicy croppedImageResizePolicy = null ) 304 | { 305 | if( image == null ) 306 | { 307 | Debug.LogError( "Image is null!" ); 308 | return; 309 | } 310 | 311 | if( !gameObject.activeSelf ) 312 | gameObject.SetActive( true ); 313 | 314 | cropCallback = onCrop; 315 | imageResizePolicy = croppedImageResizePolicy; 316 | 317 | if( settings == null ) 318 | settings = DefaultSettings; 319 | 320 | m_orientedImage.texture = image; 321 | 322 | m_originalImageSize = new Vector2( image.width, image.height ); 323 | orientedImageTransform.sizeDelta = m_originalImageSize; 324 | 325 | MarkTextureNonReadable = settings.markTextureNonReadable; 326 | OvalSelection = settings.ovalSelection; 327 | GuidelinesVisibility = settings.guidelinesVisibility; 328 | ImageBackground = settings.imageBackground; 329 | 330 | minAspectRatio = settings.selectionMinAspectRatio; 331 | maxAspectRatio = settings.selectionMaxAspectRatio; 332 | 333 | if( minAspectRatio <= 0f ) 334 | minAspectRatio = 1E-6f; 335 | if( maxAspectRatio <= 0f ) 336 | maxAspectRatio = 1E6f; 337 | 338 | if( minAspectRatio > maxAspectRatio ) 339 | { 340 | float temp = minAspectRatio; 341 | minAspectRatio = maxAspectRatio; 342 | maxAspectRatio = temp; 343 | } 344 | 345 | minSize = settings.selectionMinSize; 346 | maxSize = settings.selectionMaxSize; 347 | 348 | Vector2 maxSizeDefault = new Vector2( 2f, 2f ) * Mathf.Max( m_originalImageSize.x, m_originalImageSize.y ); 349 | if( minSize.x < 1f || minSize.y < 1f ) 350 | minSize = new Vector2( 0.1f, 0.1f ) * Mathf.Min( m_originalImageSize.x, m_originalImageSize.y ); 351 | if( maxSize.x < 1f || maxSize.y < 1f ) 352 | maxSize = maxSizeDefault; 353 | 354 | minSize = minSize.ClampBetween( Vector2.one, Vector2.one * Mathf.Max( m_originalImageSize.x, m_originalImageSize.y ) ); 355 | maxSize = maxSize.ClampBetween( minSize, maxSizeDefault ); 356 | 357 | m_autoZoomEnabled = false; 358 | SetOrientation( settings.initialOrientation ); 359 | 360 | m_autoZoomEnabled = settings.autoZoomEnabled; 361 | m_pixelPerfectSelection = settings.pixelPerfectSelection; 362 | 363 | flipHorizontalButton.SetActive( ( settings.visibleButtons & Button.FlipHorizontal ) == Button.FlipHorizontal ); 364 | flipVerticalButton.SetActive( ( settings.visibleButtons & Button.FlipVertical ) == Button.FlipVertical ); 365 | rotate90DegreesButton.SetActive( ( settings.visibleButtons & Button.Rotate90Degrees ) == Button.Rotate90Degrees ); 366 | 367 | Vector2 paddingMax = new Vector2( 1f - Mathf.Clamp01( settings.selectionInitialPaddingRight ), 1f - Mathf.Clamp01( settings.selectionInitialPaddingTop ) ); 368 | Vector2 paddingMin = new Vector2( Mathf.Clamp( settings.selectionInitialPaddingLeft, 0f, paddingMax.x ), Mathf.Clamp( settings.selectionInitialPaddingBottom, 0f, paddingMax.y ) ); 369 | 370 | UpdateSelection( Vector2.zero, m_orientedImageSize.ScaleWith( paddingMax - paddingMin ) ); 371 | m_selection.anchoredPosition = ( m_orientedImageSize - m_selection.sizeDelta ) * 0.5f; 372 | if( m_pixelPerfectSelection ) 373 | MakePixelPerfectSelection(); 374 | 375 | ResetView( false ); 376 | } 377 | 378 | public void Hide() 379 | { 380 | gameObject.SetActive( false ); 381 | 382 | m_orientedImage.texture = null; 383 | cropCallback = null; 384 | imageResizePolicy = null; 385 | } 386 | 387 | public void ResetView( bool frameSelection ) 388 | { 389 | if( m_orientedImageSize.x <= 0f || m_orientedImageSize.y <= 0f ) 390 | return; 391 | 392 | StopAutoZoom(); 393 | 394 | minImageScale = Mathf.Min( m_viewportSize.x / m_orientedImageSize.x, m_viewportSize.y / m_orientedImageSize.y ); 395 | 396 | m_imageHolder.anchoredPosition = RestrictImageToViewport( m_imageHolder.anchoredPosition, minImageScale ); 397 | m_imageHolder.localScale = new Vector3( minImageScale, minImageScale, minImageScale ); 398 | 399 | if( frameSelection && m_autoZoomEnabled ) 400 | StartAutoZoom( true ); 401 | } 402 | 403 | public void Cancel() 404 | { 405 | if( cropCallback != null ) 406 | cropCallback( false, m_orientedImage.texture, null ); 407 | 408 | Hide(); 409 | } 410 | 411 | public void Crop() 412 | { 413 | if( cropCallback != null ) 414 | { 415 | Texture2D result = CropSelection(); 416 | cropCallback( result != null, m_orientedImage.texture, result ); 417 | } 418 | 419 | Hide(); 420 | } 421 | 422 | public Texture2D CropSelection() 423 | { 424 | Vector2 selectionSize = m_selection.sizeDelta; 425 | int width = Mathf.Clamp( (int) selectionSize.x, 1, (int) m_orientedImageSize.x ); 426 | int height = Mathf.Clamp( (int) selectionSize.y, 1, (int) m_orientedImageSize.y ); 427 | if( imageResizePolicy != null ) 428 | imageResizePolicy( ref width, ref height ); 429 | 430 | if( width < 0 ) 431 | width = 1; 432 | if( height < 0 ) 433 | height = 1; 434 | 435 | return CropSelection( width, height ); 436 | } 437 | 438 | public Texture2D CropSelection( int width, int height ) 439 | { 440 | if( !gameObject.activeInHierarchy ) 441 | { 442 | Debug.LogError( "Cropper is not visible!" ); 443 | return null; 444 | } 445 | 446 | if( m_orientedImage.texture == null ) 447 | { 448 | Debug.LogError( "Cropper is not initialized!" ); 449 | return null; 450 | } 451 | 452 | // Make sure that cropped image dimensions do not exceed maximum supported texture size 453 | int maxTextureSize = SystemInfo.maxTextureSize; 454 | if( width > maxTextureSize || height > maxTextureSize ) 455 | { 456 | int preferredWidth = (int) ( maxTextureSize * width / (float) height ); 457 | int preferredHeight = (int) ( maxTextureSize * height / (float) width ); 458 | 459 | if( preferredWidth <= maxTextureSize ) 460 | preferredHeight = maxTextureSize; 461 | else 462 | preferredWidth = maxTextureSize; 463 | 464 | Debug.Log( string.Concat( width, "x", height, " is too large, using ", preferredWidth, "x", preferredHeight, " instead..." ) ); 465 | 466 | width = preferredWidth; 467 | height = preferredHeight; 468 | } 469 | 470 | RectTransform cropRenderCanvasTR = (RectTransform) cropRenderCanvas.transform; 471 | RectTransform cropRenderImageTR = (RectTransform) cropRenderImage.transform; 472 | Transform cropRenderCameraTR = cropRenderCamera.transform; 473 | 474 | cropRenderImage.texture = m_orientedImage.texture; 475 | 476 | Vector2 selectionSize = m_selection.sizeDelta; 477 | Vector2 selectionCenter = m_selection.anchoredPosition + selectionSize * 0.5f; 478 | 479 | cropRenderCanvasTR.sizeDelta = m_orientedImageSize; 480 | 481 | cropRenderSelection.anchoredPosition = m_selection.anchoredPosition; 482 | cropRenderSelection.sizeDelta = selectionSize; 483 | 484 | cropRenderImageTR.position = cropRenderCanvasTR.position; 485 | cropRenderImageTR.sizeDelta = m_originalImageSize; 486 | cropRenderImageTR.localScale = orientedImageTransform.localScale; 487 | cropRenderImageTR.localEulerAngles = orientedImageTransform.localEulerAngles; 488 | 489 | cropRenderCameraTR.eulerAngles = cropRenderCanvasTR.eulerAngles; 490 | cropRenderCameraTR.position = cropRenderCanvasTR.TransformPoint( new Vector3( selectionCenter.x - m_orientedImageSize.x * 0.5f, selectionCenter.y - m_orientedImageSize.y * 0.5f, -5f ) ); 491 | 492 | cropRenderCamera.aspect = selectionSize.x / selectionSize.y; 493 | cropRenderCamera.orthographicSize = selectionSize.y * 0.5f; 494 | 495 | Texture2D result = null; 496 | RenderTexture temp = RenderTexture.active; 497 | RenderTexture renderTex = RenderTexture.GetTemporary( width, height, 24 ); 498 | try 499 | { 500 | cropRenderCanvas.gameObject.SetActive( true ); 501 | LayoutRebuilder.ForceRebuildLayoutImmediate( cropRenderCanvasTR ); 502 | 503 | RenderTexture.active = renderTex; 504 | 505 | bool transparentBackground = ImageBackground.a < 1f; 506 | if( transparentBackground ) 507 | { 508 | cropRenderCamera.clearFlags = CameraClearFlags.Depth; 509 | GL.Clear( false, true, ImageBackground ); 510 | } 511 | else 512 | cropRenderCamera.clearFlags = CameraClearFlags.Color; 513 | 514 | cropRenderCamera.backgroundColor = ImageBackground; 515 | cropRenderCamera.targetTexture = renderTex; 516 | cropRenderCamera.Render(); 517 | 518 | result = new Texture2D( width, height, transparentBackground ? TextureFormat.RGBA32 : TextureFormat.RGB24, false ); 519 | result.ReadPixels( new Rect( 0, 0, width, height ), 0, 0, false ); 520 | result.Apply( false, MarkTextureNonReadable ); 521 | } 522 | catch( System.Exception e ) 523 | { 524 | Debug.LogException( e ); 525 | 526 | if( result != null ) 527 | { 528 | DestroyImmediate( result ); 529 | result = null; 530 | } 531 | } 532 | finally 533 | { 534 | RenderTexture.active = temp; 535 | RenderTexture.ReleaseTemporary( renderTex ); 536 | 537 | cropRenderCanvas.gameObject.SetActive( false ); 538 | cropRenderImage.texture = null; 539 | cropRenderCamera.targetTexture = null; 540 | } 541 | 542 | return result; 543 | } 544 | 545 | public void Rotate90Clockwise() 546 | { 547 | RotateClockwise( 1 ); 548 | } 549 | 550 | public void Rotate180Clockwise() 551 | { 552 | RotateClockwise( 2 ); 553 | } 554 | 555 | public void Rotate270Clockwise() 556 | { 557 | RotateClockwise( 3 ); 558 | } 559 | 560 | private void RotateClockwise( int amount ) 561 | { 562 | while( amount < 0 ) 563 | amount += 4; 564 | while( amount > 3 ) 565 | amount -= 4; 566 | 567 | if( amount == 0 ) 568 | return; 569 | 570 | int orientationInt = (int) currentOrientation; 571 | if( orientationInt < 4 ) 572 | { 573 | orientationInt -= amount; 574 | if( orientationInt < 0 ) 575 | orientationInt += 4; 576 | } 577 | else 578 | { 579 | orientationInt -= amount; 580 | if( orientationInt < 4 ) 581 | orientationInt += 4; 582 | } 583 | 584 | SetOrientation( (Orientation) orientationInt ); 585 | } 586 | 587 | public void FlipHorizontal() 588 | { 589 | if( currentOrientation == Orientation.Normal ) 590 | SetOrientation( Orientation.FlipHorizontal ); 591 | else if( currentOrientation == Orientation.FlipHorizontal ) 592 | SetOrientation( Orientation.Normal ); 593 | else if( currentOrientation == Orientation.Rotate90 ) 594 | SetOrientation( Orientation.Transverse ); 595 | else if( currentOrientation == Orientation.Transverse ) 596 | SetOrientation( Orientation.Rotate90 ); 597 | else if( currentOrientation == Orientation.Rotate180 ) 598 | SetOrientation( Orientation.FlipVertical ); 599 | else if( currentOrientation == Orientation.FlipVertical ) 600 | SetOrientation( Orientation.Rotate180 ); 601 | else if( currentOrientation == Orientation.Rotate270 ) 602 | SetOrientation( Orientation.Transpose ); 603 | else 604 | SetOrientation( Orientation.Rotate270 ); 605 | } 606 | 607 | public void FlipVertical() 608 | { 609 | if( currentOrientation == Orientation.Normal ) 610 | SetOrientation( Orientation.FlipVertical ); 611 | else if( currentOrientation == Orientation.FlipVertical ) 612 | SetOrientation( Orientation.Normal ); 613 | else if( currentOrientation == Orientation.Rotate90 ) 614 | SetOrientation( Orientation.Transpose ); 615 | else if( currentOrientation == Orientation.Transpose ) 616 | SetOrientation( Orientation.Rotate90 ); 617 | else if( currentOrientation == Orientation.Rotate180 ) 618 | SetOrientation( Orientation.FlipHorizontal ); 619 | else if( currentOrientation == Orientation.FlipHorizontal ) 620 | SetOrientation( Orientation.Rotate180 ); 621 | else if( currentOrientation == Orientation.Rotate270 ) 622 | SetOrientation( Orientation.Transverse ); 623 | else 624 | SetOrientation( Orientation.Rotate270 ); 625 | } 626 | 627 | public void SetOrientation( Orientation orientation ) 628 | { 629 | Vector2 selectionPosition = m_selection.anchoredPosition; 630 | Vector2 selectionSize = m_selection.sizeDelta; 631 | 632 | if( currentOrientation == Orientation.Normal ) 633 | { 634 | } 635 | else if( currentOrientation == Orientation.Rotate90 ) 636 | { 637 | selectionPosition = new Vector2( selectionPosition.y, m_orientedImageSize.x - selectionPosition.x - selectionSize.x ); 638 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 639 | } 640 | else if( currentOrientation == Orientation.Rotate180 ) 641 | selectionPosition = new Vector2( m_orientedImageSize.x - selectionPosition.x - selectionSize.x, m_orientedImageSize.y - selectionPosition.y - selectionSize.y ); 642 | else if( currentOrientation == Orientation.Rotate270 ) 643 | { 644 | selectionPosition = new Vector2( m_orientedImageSize.y - selectionPosition.y - selectionSize.y, selectionPosition.x ); 645 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 646 | } 647 | else if( currentOrientation == Orientation.FlipHorizontal ) 648 | selectionPosition = new Vector2( m_orientedImageSize.x - selectionPosition.x - selectionSize.x, selectionPosition.y ); 649 | else if( currentOrientation == Orientation.Transpose ) 650 | { 651 | selectionPosition = new Vector2( m_orientedImageSize.y - selectionPosition.y - selectionSize.y, m_orientedImageSize.x - selectionPosition.x - selectionSize.x ); 652 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 653 | } 654 | else if( currentOrientation == Orientation.FlipVertical ) 655 | selectionPosition = new Vector2( selectionPosition.x, m_orientedImageSize.y - selectionPosition.y - selectionSize.y ); 656 | else 657 | { 658 | selectionPosition = new Vector2( selectionPosition.y, selectionPosition.x ); 659 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 660 | } 661 | 662 | if( orientation == Orientation.Normal ) 663 | { 664 | m_orientedImageSize = m_originalImageSize; 665 | 666 | orientedImageTransform.localScale = Vector3.one; 667 | orientedImageTransform.localEulerAngles = Vector3.zero; 668 | } 669 | else if( orientation == Orientation.Rotate90 ) 670 | { 671 | m_orientedImageSize = new Vector2( m_originalImageSize.y, m_originalImageSize.x ); 672 | 673 | orientedImageTransform.localScale = Vector3.one; 674 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 90f ); 675 | 676 | selectionPosition = new Vector2( m_originalImageSize.y - selectionPosition.y - selectionSize.y, selectionPosition.x ); 677 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 678 | } 679 | else if( orientation == Orientation.Rotate180 ) 680 | { 681 | m_orientedImageSize = m_originalImageSize; 682 | 683 | orientedImageTransform.localScale = Vector3.one; 684 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 180f ); 685 | 686 | selectionPosition = new Vector2( m_originalImageSize.x - selectionPosition.x - selectionSize.x, m_originalImageSize.y - selectionPosition.y - selectionSize.y ); 687 | } 688 | else if( orientation == Orientation.Rotate270 ) 689 | { 690 | m_orientedImageSize = new Vector2( m_originalImageSize.y, m_originalImageSize.x ); 691 | 692 | orientedImageTransform.localScale = Vector3.one; 693 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 270f ); 694 | 695 | selectionPosition = new Vector2( selectionPosition.y, m_originalImageSize.x - selectionPosition.x - selectionSize.x ); 696 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 697 | } 698 | else if( orientation == Orientation.FlipHorizontal ) 699 | { 700 | m_orientedImageSize = m_originalImageSize; 701 | 702 | orientedImageTransform.localScale = new Vector3( -1f, 1f, 1f ); 703 | orientedImageTransform.localEulerAngles = Vector3.zero; 704 | 705 | selectionPosition = new Vector2( m_originalImageSize.x - selectionPosition.x - selectionSize.x, selectionPosition.y ); 706 | } 707 | else if( orientation == Orientation.Transpose ) 708 | { 709 | m_orientedImageSize = new Vector2( m_originalImageSize.y, m_originalImageSize.x ); 710 | 711 | orientedImageTransform.localScale = new Vector3( -1f, 1f, 1f ); 712 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 90f ); 713 | 714 | selectionPosition = new Vector2( m_originalImageSize.y - selectionPosition.y - selectionSize.y, m_originalImageSize.x - selectionPosition.x - selectionSize.x ); 715 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 716 | } 717 | else if( orientation == Orientation.FlipVertical ) 718 | { 719 | m_orientedImageSize = m_originalImageSize; 720 | 721 | orientedImageTransform.localScale = new Vector3( -1f, 1f, 1f ); 722 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 180f ); 723 | 724 | selectionPosition = new Vector2( selectionPosition.x, m_originalImageSize.y - selectionPosition.y - selectionSize.y ); 725 | } 726 | else 727 | { 728 | m_orientedImageSize = new Vector2( m_originalImageSize.y, m_originalImageSize.x ); 729 | 730 | orientedImageTransform.localScale = new Vector3( -1f, 1f, 1f ); 731 | orientedImageTransform.localEulerAngles = new Vector3( 0f, 0f, 270f ); 732 | 733 | selectionPosition = new Vector2( selectionPosition.y, selectionPosition.x ); 734 | selectionSize = new Vector2( selectionSize.y, selectionSize.x ); 735 | } 736 | 737 | m_imageHolder.sizeDelta = m_orientedImageSize; 738 | 739 | currMinSize = minSize.ClampBetween( Vector2.one, m_orientedImageSize ); 740 | currMaxSize = maxSize.ClampBetween( currMinSize, m_orientedImageSize ); 741 | 742 | // If new selection doesn't comply with limiting aspect ratios, keep original selection 743 | float aspectRatio = selectionSize.x / selectionSize.y; 744 | if( aspectRatio < minAspectRatio - 1E-4f ) // consider floating-point precision 745 | { 746 | // Pixel-perfect selection may not always comply with limiting aspect ratios 747 | if( !m_pixelPerfectSelection || ( selectionSize.x + 1 ) / ( selectionSize.y - 1 ) < minAspectRatio - 1E-4f ) 748 | selectionSize = m_selection.sizeDelta; 749 | } 750 | else if( aspectRatio > maxAspectRatio + 1E-4f ) 751 | { 752 | if( !m_pixelPerfectSelection || ( selectionSize.x - 1 ) / ( selectionSize.y + 1 ) > maxAspectRatio + 1E-4f ) 753 | selectionSize = m_selection.sizeDelta; 754 | } 755 | 756 | UpdateSelection( selectionPosition, selectionSize ); 757 | if( m_pixelPerfectSelection ) 758 | MakePixelPerfectSelection(); 759 | 760 | currentOrientation = orientation; 761 | ResetView( true ); 762 | } 763 | 764 | public void StartAutoZoom( bool instantZoom ) 765 | { 766 | if( !gameObject.activeInHierarchy ) 767 | return; 768 | 769 | StopAutoZoom(); 770 | 771 | Vector2 selectionSize = m_selection.sizeDelta; 772 | Vector2 selectionSizeScaled = selectionSize * m_imageHolder.localScale.z; 773 | 774 | float zoomAmount = -1f; 775 | float scale = m_imageHolder.localScale.z; 776 | float fillRate = Mathf.Max( selectionSizeScaled.x / m_viewportSize.x, selectionSizeScaled.y / m_viewportSize.y ); 777 | if( fillRate <= autoZoomInThreshold ) 778 | { 779 | float scaleX = m_viewportSize.x * autoZoomInFillAmount / selectionSize.x; 780 | float scaleY = m_viewportSize.y * autoZoomInFillAmount / selectionSize.y; 781 | 782 | zoomAmount = Mathf.Min( scaleX, scaleY ); 783 | } 784 | else if( fillRate >= autoZoomOutThreshold ) 785 | { 786 | float scaleX = m_viewportSize.x * autoZoomOutFillAmount / selectionSize.x; 787 | float scaleY = m_viewportSize.y * autoZoomOutFillAmount / selectionSize.y; 788 | 789 | zoomAmount = Mathf.Min( scaleX, scaleY ); 790 | } 791 | else 792 | { 793 | Vector2 selectionBottomLeft = m_imageHolder.anchoredPosition + m_selection.anchoredPosition * scale; 794 | Vector2 selectionTopRight = selectionBottomLeft + m_selection.sizeDelta * scale; 795 | 796 | // If part of the selection rests outside of the viewport, engage auto-zoom 797 | if( selectionBottomLeft.x < -1E-4f || selectionBottomLeft.y < -1E-4f || selectionTopRight.x > m_viewportSize.x + 1E-4f || selectionTopRight.y > m_viewportSize.y + 1E-4f ) 798 | zoomAmount = scale; 799 | } 800 | 801 | if( zoomAmount < 0f ) 802 | return; 803 | 804 | if( zoomAmount < minImageScale ) 805 | zoomAmount = minImageScale; 806 | 807 | if( Mathf.Abs( zoomAmount - scale ) < 0.001f ) 808 | instantZoom = true; 809 | 810 | autoZoomCoroutine = AutoZoom( zoomAmount, instantZoom ); 811 | StartCoroutine( autoZoomCoroutine ); 812 | } 813 | 814 | private IEnumerator AutoZoom( float zoomAmount, bool instantZoom ) 815 | { 816 | float elapsed = 0f; 817 | float length = autoZoomCurve.length == 0 ? 0f : autoZoomCurve[autoZoomCurve.length - 1].time; 818 | 819 | Vector3 initialScale = m_imageHolder.localScale; 820 | Vector2 initialPosition = m_imageHolder.anchoredPosition; 821 | 822 | Vector3 finalScale = new Vector3( zoomAmount, zoomAmount, zoomAmount ); 823 | Vector2 finalPosition = m_viewportSize * 0.5f - ( m_selection.anchoredPosition + m_selection.sizeDelta * 0.5f ) * finalScale.z; 824 | 825 | finalPosition = RestrictImageToViewport( finalPosition, zoomAmount ); 826 | 827 | if( !instantZoom && elapsed < length ) 828 | { 829 | Vector2 deltaPosition = finalPosition - initialPosition; 830 | Vector3 deltaScale = finalScale - initialScale; 831 | 832 | while( elapsed < length ) 833 | { 834 | yield return null; 835 | elapsed += Time.unscaledDeltaTime; 836 | if( elapsed >= length ) 837 | break; 838 | 839 | float modifier = autoZoomCurve.Evaluate( elapsed ); 840 | 841 | m_imageHolder.anchoredPosition = initialPosition + deltaPosition * modifier; 842 | m_imageHolder.localScale = initialScale + deltaScale * modifier; 843 | } 844 | } 845 | 846 | m_imageHolder.anchoredPosition = finalPosition; 847 | m_imageHolder.localScale = finalScale; 848 | 849 | autoZoomCoroutine = null; 850 | } 851 | 852 | private void StopAutoZoom() 853 | { 854 | if( autoZoomCoroutine != null ) 855 | { 856 | StopCoroutine( autoZoomCoroutine ); 857 | autoZoomCoroutine = null; 858 | } 859 | 860 | if( currentSelectionHandler != null ) 861 | { 862 | currentSelectionHandler.Stop(); 863 | currentSelectionHandler = null; 864 | } 865 | } 866 | 867 | public bool CanModifySelectionWith( ISelectionHandler handler ) 868 | { 869 | if( autoZoomCoroutine != null ) 870 | return false; 871 | 872 | if( handler != currentSelectionHandler ) 873 | { 874 | if( currentSelectionHandler != null ) 875 | currentSelectionHandler.Stop(); 876 | 877 | currentSelectionHandler = handler; 878 | } 879 | 880 | if( m_guidelinesVisibility == Visibility.OnDrag ) 881 | { 882 | for( int i = 0; i < guidelines.Length; i++ ) 883 | guidelines[i].enabled = true; 884 | } 885 | 886 | return true; 887 | } 888 | 889 | public void StopModifySelectionWith( ISelectionHandler handler ) 890 | { 891 | if( currentSelectionHandler == handler ) 892 | { 893 | currentSelectionHandler = null; 894 | 895 | if( m_guidelinesVisibility == Visibility.OnDrag ) 896 | { 897 | for( int i = 0; i < guidelines.Length; i++ ) 898 | guidelines[i].enabled = false; 899 | } 900 | 901 | if( m_pixelPerfectSelection ) 902 | MakePixelPerfectSelection(); 903 | 904 | if( m_autoZoomEnabled ) 905 | StartAutoZoom( false ); 906 | } 907 | } 908 | 909 | public void MakePixelPerfectSelection() 910 | { 911 | Vector2 size = m_selection.sizeDelta.RoundToInt().ClampBetween( currMinSize.CeilToInt(), m_orientedImageSize ); 912 | float aspectRatio = size.x / size.y; 913 | if( aspectRatio < minAspectRatio ) 914 | { 915 | bool foundMatchingSize = false; 916 | bool canExpandWidth = size.x < m_orientedImageSize.x; 917 | bool canShrinkHeight = size.y > Mathf.CeilToInt( currMinSize.y ); 918 | 919 | if( canExpandWidth ) 920 | { 921 | aspectRatio = ( size.x + 1 ) / size.y; 922 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 923 | { 924 | size.x = size.x + 1; 925 | foundMatchingSize = true; 926 | } 927 | } 928 | 929 | if( !foundMatchingSize && canShrinkHeight ) 930 | { 931 | aspectRatio = size.x / ( size.y - 1 ); 932 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 933 | { 934 | size.y = size.y - 1; 935 | foundMatchingSize = true; 936 | } 937 | } 938 | 939 | if( !foundMatchingSize && canExpandWidth && canShrinkHeight ) 940 | { 941 | aspectRatio = ( size.x + 1 ) / ( size.y - 1 ); 942 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 943 | { 944 | size.x = size.x + 1; 945 | size.y = size.y - 1; 946 | } 947 | } 948 | } 949 | else if( aspectRatio > maxAspectRatio ) 950 | { 951 | bool foundMatchingSize = false; 952 | bool canShrinkWidth = size.x > Mathf.CeilToInt( currMinSize.x ); 953 | bool canExpandHeight = size.y < m_orientedImageSize.y; 954 | 955 | if( !foundMatchingSize && canShrinkWidth ) 956 | { 957 | aspectRatio = ( size.x - 1 ) / size.y; 958 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 959 | { 960 | size.x = size.x - 1; 961 | foundMatchingSize = true; 962 | } 963 | } 964 | 965 | if( canExpandHeight ) 966 | { 967 | aspectRatio = size.x / ( size.y + 1 ); 968 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 969 | { 970 | size.y = size.y + 1; 971 | foundMatchingSize = true; 972 | } 973 | } 974 | 975 | if( !foundMatchingSize && canShrinkWidth && canExpandHeight ) 976 | { 977 | aspectRatio = ( size.x - 1 ) / ( size.y + 1 ); 978 | if( aspectRatio >= minAspectRatio && aspectRatio <= maxAspectRatio ) 979 | { 980 | size.x = size.x - 1; 981 | size.y = size.y + 1; 982 | } 983 | } 984 | } 985 | 986 | m_selection.anchoredPosition = m_selection.anchoredPosition.RoundToInt().ClampBetween( Vector2.zero, m_orientedImageSize - size ); 987 | m_selection.sizeDelta = size; 988 | } 989 | 990 | public void UpdateSelection( Vector2 position ) 991 | { 992 | m_selection.anchoredPosition = position.ClampBetween( Vector2.zero, m_orientedImageSize - m_selection.sizeDelta ); 993 | } 994 | 995 | public void UpdateSelection( Vector2 position, Vector2 size, Direction pivot = Direction.None, bool shrinkToFit = true ) 996 | { 997 | Vector2 newSize = size.ClampBetween( currMinSize, currMaxSize ); 998 | 999 | float aspectRatio = newSize.x / newSize.y; 1000 | if( aspectRatio < minAspectRatio ) 1001 | { 1002 | if( shrinkToFit ) 1003 | { 1004 | float requiredHeight = newSize.x / minAspectRatio; 1005 | if( requiredHeight >= currMinSize.y ) 1006 | newSize.y = requiredHeight; 1007 | else 1008 | { 1009 | float requiredWidth = currMinSize.y * minAspectRatio; 1010 | if( requiredWidth <= currMaxSize.x ) 1011 | newSize = new Vector2( requiredWidth, currMinSize.y ); 1012 | else 1013 | newSize = new Vector2( currMaxSize.x, currMinSize.y ); 1014 | } 1015 | } 1016 | else 1017 | { 1018 | float requiredWidth = newSize.y * minAspectRatio; 1019 | if( requiredWidth <= currMaxSize.x ) 1020 | newSize.x = requiredWidth; 1021 | else 1022 | { 1023 | float requiredHeight = currMaxSize.x / minAspectRatio; 1024 | if( requiredHeight <= currMaxSize.y ) 1025 | newSize = new Vector2( currMaxSize.x, requiredHeight ); 1026 | else 1027 | newSize = currMaxSize; 1028 | } 1029 | } 1030 | } 1031 | else if( aspectRatio > maxAspectRatio ) 1032 | { 1033 | if( shrinkToFit ) 1034 | { 1035 | float requiredWidth = newSize.y * maxAspectRatio; 1036 | if( requiredWidth >= currMinSize.x ) 1037 | newSize.x = requiredWidth; 1038 | else 1039 | { 1040 | float requiredHeight = currMinSize.x / maxAspectRatio; 1041 | if( requiredHeight <= currMaxSize.y ) 1042 | newSize = new Vector2( currMinSize.x, requiredHeight ); 1043 | else 1044 | newSize = new Vector2( currMinSize.x, currMaxSize.y ); 1045 | } 1046 | } 1047 | else 1048 | { 1049 | float requiredHeight = newSize.x / maxAspectRatio; 1050 | if( requiredHeight <= currMaxSize.y ) 1051 | newSize.y = requiredHeight; 1052 | else 1053 | { 1054 | float requiredWidth = currMaxSize.y * maxAspectRatio; 1055 | if( requiredWidth <= currMaxSize.x ) 1056 | newSize = new Vector2( requiredWidth, currMaxSize.y ); 1057 | else 1058 | newSize = currMaxSize; 1059 | } 1060 | } 1061 | } 1062 | 1063 | if( size.x != newSize.x ) 1064 | { 1065 | if( ( pivot & Direction.Right ) == Direction.Right ) 1066 | position.x -= newSize.x - size.x; 1067 | else if( ( pivot & Direction.Left ) != Direction.Left ) 1068 | position.x -= ( newSize.x - size.x ) * 0.5f; 1069 | 1070 | size.x = newSize.x; 1071 | } 1072 | if( size.y != newSize.y ) 1073 | { 1074 | if( ( pivot & Direction.Top ) == Direction.Top ) 1075 | position.y -= newSize.y - size.y; 1076 | else if( ( pivot & Direction.Bottom ) != Direction.Bottom ) 1077 | position.y -= ( newSize.y - size.y ) * 0.5f; 1078 | 1079 | size.y = newSize.y; 1080 | } 1081 | 1082 | m_selection.anchoredPosition = position.ClampBetween( Vector2.zero, m_orientedImageSize - size ); 1083 | m_selection.sizeDelta = size; 1084 | } 1085 | 1086 | private Vector2 RestrictImageToViewport( Vector2 position, float scale ) 1087 | { 1088 | Vector2 sizeScaled = m_imageHolder.sizeDelta * scale; 1089 | 1090 | if( sizeScaled.x < m_viewportSize.x ) 1091 | position.x = ( m_viewportSize.x - sizeScaled.x ) * 0.5f; 1092 | else 1093 | position.x = Mathf.Clamp( position.x, m_viewportSize.x - sizeScaled.x, 0f ); 1094 | 1095 | if( sizeScaled.y < m_viewportSize.y ) 1096 | position.y = ( m_viewportSize.y - sizeScaled.y ) * 0.5f; 1097 | else 1098 | position.y = Mathf.Clamp( position.y, m_viewportSize.y - sizeScaled.y, 0f ); 1099 | 1100 | return position; 1101 | } 1102 | 1103 | public Vector2 ScrollImage( Vector2 imagePosition, Direction direction ) 1104 | { 1105 | if( direction == Direction.Left ) 1106 | { 1107 | imagePosition.x += viewportScrollSpeed * Time.unscaledDeltaTime; 1108 | if( imagePosition.x > 0f ) 1109 | imagePosition.x = 0f; 1110 | } 1111 | else if( direction == Direction.Top ) 1112 | { 1113 | float imageHeight = m_imageHolder.sizeDelta.y * m_imageHolder.localScale.z; 1114 | imagePosition.y -= viewportScrollSpeed * Time.unscaledDeltaTime; 1115 | if( imagePosition.y + imageHeight < m_viewportSize.y ) 1116 | imagePosition.y = m_viewportSize.y - imageHeight; 1117 | } 1118 | else if( direction == Direction.Right ) 1119 | { 1120 | float imageWidth = m_imageHolder.sizeDelta.x * m_imageHolder.localScale.z; 1121 | imagePosition.x -= viewportScrollSpeed * Time.unscaledDeltaTime; 1122 | if( imagePosition.x + imageWidth < m_viewportSize.x ) 1123 | imagePosition.x = m_viewportSize.x - imageWidth; 1124 | } 1125 | else 1126 | { 1127 | imagePosition.y += viewportScrollSpeed * Time.unscaledDeltaTime; 1128 | if( imagePosition.y > 0f ) 1129 | imagePosition.y = 0f; 1130 | } 1131 | 1132 | return imagePosition; 1133 | } 1134 | 1135 | public Vector2 GetTouchPosition( Vector2 screenPos, Camera cam ) 1136 | { 1137 | Vector2 localPos; 1138 | RectTransformUtility.ScreenPointToLocalPointInRectangle( m_imageHolder, screenPos, cam, out localPos ); 1139 | 1140 | return localPos; 1141 | } 1142 | } --------------------------------------------------------------------------------