[
  {
    "path": ".github/README.md",
    "content": "# Unity 360° Screenshot Capture\n\n**Available on Asset Store:** https://assetstore.unity.com/packages/tools/camera/360-screenshot-capture-112864\n\n**Forum Thread:** https://forum.unity.com/threads/360-screenshot-capture-open-source.501310/\n\n**Discord:** https://discord.gg/UJJt549AaV\n\n**[GitHub Sponsors ☕](https://github.com/sponsors/yasirkula)**\n\nThis simple script captures a **360° photo** with your Unity camera and injects the necessary **XMP metadata** to it; so the output image supports 360° viewers on the web out-of-the-box (like *Facebook* and *Flickr*). Both **JPEG** and **PNG** formats are supported.\n\nThe raw image is in equirectangular form. Here is an example screenshot [(it looks like this when uploaded to Flickr)](https://flic.kr/p/VPxPwY):\n\n![screenshot](Images/360render.jpeg)\n\n## INSTALLATION\n\nThere are 5 ways to install this plugin:\n\n- import [360Screenshot.unitypackage](https://github.com/yasirkula/Unity360ScreenshotCapture/releases) via *Assets-Import Package*\n- clone/[download](https://github.com/yasirkula/Unity360ScreenshotCapture/archive/master.zip) this repository and move the *Plugins* folder to your Unity project's *Assets* folder\n- import it from [Asset Store](https://assetstore.unity.com/packages/tools/camera/360-screenshot-capture-112864)\n- *(via Package Manager)* click the + button and install the package from the following git URL:\n  - `https://github.com/yasirkula/Unity360ScreenshotCapture.git`\n- *(via [OpenUPM](https://openupm.com))* after installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command:\n  - `openupm add com.yasirkula.screenshotcapture`\n\n## HOW TO\n\nSimply call the `I360Render.Capture()` or `I360Render.CaptureAsync()` (Unity 2018.2 or later) function in your scripts. Their signatures are as follows:\n\n```csharp\npublic static byte[] Capture( int width = 1024, bool encodeAsJPEG = true, Camera renderCam = null, bool faceCameraDirection = true );\n\n// !!! Async version uses AsyncGPUReadback.Request so it won't work on all platforms or Graphics APIs !!!\npublic static void CaptureAsync( Action<byte[]> callback, int width = 1024, bool encodeAsJPEG = true, Camera renderCam = null, bool faceCameraDirection = true );\n```\n\n- **width**: The width of the resulting image. It must be a power of 2. The height will be equal to *width / 2*. Be aware that maximum allowed image width is 8192 pixels\n- **encodeAsJPEG**: determines whether the image will be encoded as *JPEG* or *PNG*\n- **renderCam**: the camera that will be used to render the 360° image. If set to null, *Camera.main* will be used\n- **faceCameraDirection**: if set to *true*, when the 360° image is viewed in a 360° viewer, initial camera rotation will match the rotation of the *renderCam*. Otherwise, initial camera rotation will be *Quaternion.identity* (facing Z+ axis)\n\nThese functions return a **byte[]** object either directly or as a callback; you can write these bytes to a file using `File.WriteAllBytes` (see example code below).\n\n## FAQ\n\n- **Objects are rendered inside out in the 360° screenshot**\n\nThis is usually caused by 3rd-party plugins that change the value of `GL.invertCulling` (e.g. mirrors). See the solution: https://forum.unity.com/threads/360-screenshot-capture-open-source.501310/#post-7078093\n\n- **360° screenshot is blank on Oculus Quest 2**\n\nTry using the `CaptureAsync` function instead of `Capture`.\n\n## EXAMPLE CODE\n\n```csharp\nusing System.IO;\nusing UnityEngine;\n\npublic class RenderTest : MonoBehaviour\n{\n\tpublic int imageWidth = 1024;\n\tpublic bool saveAsJPEG = true;\n\n\tvoid Update()\n\t{\n\t\tif( Input.GetKeyDown( KeyCode.P ) )\n\t\t{\n\t\t\tbyte[] bytes = I360Render.Capture( imageWidth, saveAsJPEG );\n\t\t\tif( bytes != null )\n\t\t\t{\n\t\t\t\tstring path = Path.Combine( Application.persistentDataPath, \"360render\" + ( saveAsJPEG ? \".jpeg\" : \".png\" ) );\n\t\t\t\tFile.WriteAllBytes( path, bytes );\n\t\t\t\tDebug.Log( \"360 render saved to \" + path );\n\t\t\t}\n\t\t}\n\t}\n}\n```\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2020 Süleyman Yasir KULA\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LICENSE.txt.meta",
    "content": "fileFormatVersion: 2\nguid: 308ff69723b9a1f4f9cf961b4d1c04bc\nTextScriptImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render/I360Render.cs",
    "content": "﻿using System;\nusing UnityEngine;\nusing UnityEngine.Rendering;\n\npublic static class I360Render\n{\n\tprivate static Material equirectangularConverter = null;\n\tprivate static int paddingX;\n\n\tpublic static byte[] Capture( int width = 1024, bool encodeAsJPEG = true, Camera renderCam = null, bool faceCameraDirection = true )\n\t{\n\t\treturn CaptureInternal( width, encodeAsJPEG, renderCam, faceCameraDirection );\n\t}\n\n\tpublic static void CaptureAsync( Action<byte[]> callback, int width = 1024, bool encodeAsJPEG = true, Camera renderCam = null, bool faceCameraDirection = true )\n\t{\n\t\tCaptureInternal( width, encodeAsJPEG, renderCam, faceCameraDirection, callback );\n\t}\n\n\tprivate static byte[] CaptureInternal( int width = 1024, bool encodeAsJPEG = true, Camera renderCam = null, bool faceCameraDirection = true, Action<byte[]> asyncCallback = null )\n\t{\n\t\tif( renderCam == null )\n\t\t{\n\t\t\trenderCam = Camera.main;\n\t\t\tif( renderCam == null )\n\t\t\t{\n\t\t\t\tDebug.LogError( \"Error: no camera detected\" );\n\n\t\t\t\tif( asyncCallback != null )\n\t\t\t\t\tasyncCallback( null );\n\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\tRenderTexture camTarget = renderCam.targetTexture;\n\n\t\tif( equirectangularConverter == null )\n\t\t{\n\t\t\tequirectangularConverter = new Material( Shader.Find( \"Hidden/I360CubemapToEquirectangular\" ) );\n\t\t\tpaddingX = Shader.PropertyToID( \"_PaddingX\" );\n\t\t}\n\n\t\tbool asyncOperationStarted = false;\n\t\tint cubemapSize = Mathf.Min( Mathf.NextPowerOfTwo( width ), 8192 );\n\t\tRenderTexture activeRT = RenderTexture.active;\n\t\tRenderTexture cubemap = null, equirectangularTexture = null;\n\t\tTexture2D output = null;\n\t\ttry\n\t\t{\n            RenderTextureDescriptor desc = new(cubemapSize, cubemapSize, RenderTextureFormat.ARGB32, 0) { dimension = TextureDimension.Cube };\n            cubemap = RenderTexture.GetTemporary(desc);\n            equirectangularTexture = RenderTexture.GetTemporary(cubemapSize, cubemapSize / 2, 0);\n\n\t\t\tif( !renderCam.RenderToCubemap( cubemap, 63 ) )\n\t\t\t{\n\t\t\t\tDebug.LogError( \"Rendering to cubemap is not supported on device/platform!\" );\n\n\t\t\t\tif( asyncCallback != null )\n\t\t\t\t\tasyncCallback( null );\n\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tequirectangularConverter.SetFloat( paddingX, faceCameraDirection ? ( renderCam.transform.eulerAngles.y / 360f ) : 0f );\n\t\t\tGraphics.Blit( cubemap, equirectangularTexture, equirectangularConverter );\n\n\t\t\tif( asyncCallback != null )\n\t\t\t{\n\t\t\t\tAsyncGPUReadback.Request( equirectangularTexture, 0, TextureFormat.RGB24, ( asyncResult ) =>\n\t\t\t\t{\n\t\t\t\t\ttry\n\t\t\t\t\t{\n\t\t\t\t\t\toutput = new Texture2D( equirectangularTexture.width, equirectangularTexture.height, TextureFormat.RGB24, false );\n\t\t\t\t\t\tif( !asyncResult.hasError )\n\t\t\t\t\t\t\toutput.LoadRawTextureData( asyncResult.GetData<byte>() );\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDebug.LogError( \"Async thumbnail request failed, falling back to conventional method\" );\n\n\t\t\t\t\t\t\tRenderTexture _activeRT = RenderTexture.active;\n\t\t\t\t\t\t\ttry\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tRenderTexture.active = equirectangularTexture;\n\t\t\t\t\t\t\t\toutput.ReadPixels( new Rect( 0, 0, equirectangularTexture.width, equirectangularTexture.height ), 0, 0 );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfinally\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tRenderTexture.active = _activeRT;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tasyncCallback( encodeAsJPEG ? InsertXMPIntoTexture2D_JPEG( output ) : InsertXMPIntoTexture2D_PNG( output ) );\n\t\t\t\t\t}\n\t\t\t\t\tfinally\n\t\t\t\t\t{\n\t\t\t\t\t\tif( equirectangularTexture )\n\t\t\t\t\t\t\tRenderTexture.ReleaseTemporary( equirectangularTexture );\n\n\t\t\t\t\t\tif( output )\n\t\t\t\t\t\t\tUnityEngine.Object.DestroyImmediate( output );\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\t\tasyncOperationStarted = true;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tRenderTexture.active = equirectangularTexture;\n\t\t\t\toutput = new Texture2D( equirectangularTexture.width, equirectangularTexture.height, TextureFormat.RGB24, false );\n\t\t\t\toutput.ReadPixels( new Rect( 0, 0, equirectangularTexture.width, equirectangularTexture.height ), 0, 0 );\n\t\t\t\treturn encodeAsJPEG ? InsertXMPIntoTexture2D_JPEG( output ) : InsertXMPIntoTexture2D_PNG( output );\n\t\t\t}\n\t\t}\n\t\tcatch( Exception e )\n\t\t{\n\t\t\tDebug.LogException( e );\n\n\t\t\tif( !asyncOperationStarted && asyncCallback != null )\n\t\t\t\tasyncCallback( null );\n\n\t\t\treturn null;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\trenderCam.targetTexture = camTarget;\n\n\t\t\tif( !asyncOperationStarted )\n\t\t\t\tRenderTexture.active = activeRT;\n\n\t\t\tif( cubemap )\n\t\t\t\tRenderTexture.ReleaseTemporary( cubemap );\n\n\t\t\tif( equirectangularTexture )\n\t\t\t{\n\t\t\t\tif( !asyncOperationStarted )\n\t\t\t\t\tRenderTexture.ReleaseTemporary( equirectangularTexture );\n\t\t\t}\n\n\t\t\tif( output )\n\t\t\t{\n\t\t\t\tif( !asyncOperationStarted )\n\t\t\t\t\tUnityEngine.Object.DestroyImmediate( output );\n\t\t\t}\n\t\t}\n\t}\n\n\t#region XMP Injection\n\tprivate const string XMP_NAMESPACE_JPEG = \"http://ns.adobe.com/xap/1.0/\";\n\tprivate const string XMP_CONTENT_TO_FORMAT_JPEG = \"<x:xmpmeta xmlns:x=\\\"adobe:ns:meta/\\\" x:xmptk=\\\"Adobe XMP Core 5.1.0-jc003\\\"> <rdf:RDF xmlns:rdf=\\\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\\\"> <rdf:Description rdf:about=\\\"\\\" xmlns:GPano=\\\"http://ns.google.com/photos/1.0/panorama/\\\" GPano:UsePanoramaViewer=\\\"True\\\" GPano:CaptureSoftware=\\\"Unity3D\\\" GPano:StitchingSoftware=\\\"Unity3D\\\" GPano:ProjectionType=\\\"equirectangular\\\" GPano:PoseHeadingDegrees=\\\"180.0\\\" GPano:InitialViewHeadingDegrees=\\\"0.0\\\" GPano:InitialViewPitchDegrees=\\\"0.0\\\" GPano:InitialViewRollDegrees=\\\"0.0\\\" GPano:InitialHorizontalFOVDegrees=\\\"{0}\\\" GPano:CroppedAreaLeftPixels=\\\"0\\\" GPano:CroppedAreaTopPixels=\\\"0\\\" GPano:CroppedAreaImageWidthPixels=\\\"{1}\\\" GPano:CroppedAreaImageHeightPixels=\\\"{2}\\\" GPano:FullPanoWidthPixels=\\\"{1}\\\" GPano:FullPanoHeightPixels=\\\"{2}\\\"/></rdf:RDF></x:xmpmeta>\";\n\tprivate const string XMP_CONTENT_TO_FORMAT_PNG = \"XML:com.adobe.xmp\\0\\0\\0\\0\\0<?xpacket begin=\\\"ï»¿\\\" id=\\\"W5M0MpCehiHzreSzNTczkc9d\\\"?><x:xmpmeta xmlns:x=\\\"adobe:ns:meta/\\\" x:xmptk=\\\"Adobe XMP Core 5.1.0-jc003\\\"> <rdf:RDF xmlns:rdf=\\\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\\\"> <rdf:Description rdf:about=\\\"\\\" xmlns:GPano=\\\"http://ns.google.com/photos/1.0/panorama/\\\" xmlns:xmp=\\\"http://ns.adobe.com/xap/1.0/\\\" xmlns:dc=\\\"http://purl.org/dc/elements/1.1/\\\" xmlns:xmpMM=\\\"http://ns.adobe.com/xap/1.0/mm/\\\" xmlns:stEvt=\\\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\\\" xmlns:tiff=\\\"http://ns.adobe.com/tiff/1.0/\\\" xmlns:exif=\\\"http://ns.adobe.com/exif/1.0/\\\"> <GPano:UsePanoramaViewer>True</GPano:UsePanoramaViewer> <GPano:CaptureSoftware>Unity3D</GPano:CaptureSoftware> <GPano:StitchingSoftware>Unity3D</GPano:StitchingSoftware> <GPano:ProjectionType>equirectangular</GPano:ProjectionType> <GPano:PoseHeadingDegrees>180.0</GPano:PoseHeadingDegrees> <GPano:InitialViewHeadingDegrees>0.0</GPano:InitialViewHeadingDegrees> <GPano:InitialViewPitchDegrees>0.0</GPano:InitialViewPitchDegrees> <GPano:InitialViewRollDegrees>0.0</GPano:InitialViewRollDegrees> <GPano:InitialHorizontalFOVDegrees>{0}</GPano:InitialHorizontalFOVDegrees> <GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels> <GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels> <GPano:CroppedAreaImageWidthPixels>{1}</GPano:CroppedAreaImageWidthPixels> <GPano:CroppedAreaImageHeightPixels>{2}</GPano:CroppedAreaImageHeightPixels> <GPano:FullPanoWidthPixels>{1}</GPano:FullPanoWidthPixels> <GPano:FullPanoHeightPixels>{2}</GPano:FullPanoHeightPixels> <tiff:Orientation>1</tiff:Orientation> <exif:PixelXDimension>{1}</exif:PixelXDimension> <exif:PixelYDimension>{2}</exif:PixelYDimension> </rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end=\\\"w\\\"?>\";\n\tprivate static uint[] CRC_TABLE_PNG = null;\n\n\tpublic static byte[] InsertXMPIntoTexture2D_JPEG( Texture2D image )\n\t{\n\t\treturn DoTheHardWork_JPEG( image.EncodeToJPG( 100 ), image.width, image.height );\n\t}\n\n\tpublic static byte[] InsertXMPIntoTexture2D_PNG( Texture2D image )\n\t{\n\t\treturn DoTheHardWork_PNG( image.EncodeToPNG(), image.width, image.height );\n\t}\n\n\t#region JPEG Encoding\n\tprivate static byte[] DoTheHardWork_JPEG( byte[] fileBytes, int imageWidth, int imageHeight )\n\t{\n\t\tint xmpIndex = 0, xmpContentSize = 0;\n\t\twhile( !SearchChunkForXMP_JPEG( fileBytes, ref xmpIndex, ref xmpContentSize ) )\n\t\t{\n\t\t\tif( xmpIndex == -1 )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tint copyBytesUntil, copyBytesFrom;\n\t\tif( xmpIndex == -1 )\n\t\t{\n\t\t\tcopyBytesUntil = copyBytesFrom = FindIndexToInsertXMPCode_JPEG( fileBytes );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcopyBytesUntil = xmpIndex;\n\t\t\tcopyBytesFrom = xmpIndex + 2 + xmpContentSize;\n\t\t}\n\n\t\tstring xmpContent = string.Concat( XMP_NAMESPACE_JPEG, \"\\0\", string.Format( XMP_CONTENT_TO_FORMAT_JPEG, 75f.ToString( \"F1\" ), imageWidth, imageHeight ) );\n\t\tint xmpLength = xmpContent.Length + 2;\n\t\txmpContent = string.Concat( (char) 0xFF, (char) 0xE1, (char) ( xmpLength / 256 ), (char) ( xmpLength % 256 ), xmpContent );\n\n\t\tbyte[] result = new byte[copyBytesUntil + xmpContent.Length + ( fileBytes.Length - copyBytesFrom )];\n\n\t\tArray.Copy( fileBytes, 0, result, 0, copyBytesUntil );\n\n\t\tfor( int i = 0; i < xmpContent.Length; i++ )\n\t\t{\n\t\t\tresult[copyBytesUntil + i] = (byte) xmpContent[i];\n\t\t}\n\n\t\tArray.Copy( fileBytes, copyBytesFrom, result, copyBytesUntil + xmpContent.Length, fileBytes.Length - copyBytesFrom );\n\n\t\treturn result;\n\t}\n\n\tprivate static bool CheckBytesForXMPNamespace_JPEG( byte[] bytes, int startIndex )\n\t{\n\t\tfor( int i = 0; i < XMP_NAMESPACE_JPEG.Length; i++ )\n\t\t{\n\t\t\tif( bytes[startIndex + i] != XMP_NAMESPACE_JPEG[i] )\n\t\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate static bool SearchChunkForXMP_JPEG( byte[] bytes, ref int startIndex, ref int chunkSize )\n\t{\n\t\tif( startIndex + 4 < bytes.Length )\n\t\t{\n\t\t\t//Debug.Log( startIndex + \" \" + System.Convert.ToByte( bytes[startIndex] ).ToString( \"x2\" ) + \" \" + System.Convert.ToByte( bytes[startIndex+1] ).ToString( \"x2\" ) + \" \" +\n\t\t\t//           System.Convert.ToByte( bytes[startIndex+2] ).ToString( \"x2\" ) + \" \" + System.Convert.ToByte( bytes[startIndex+3] ).ToString( \"x2\" ) );\n\n\t\t\tif( bytes[startIndex] == 0xFF )\n\t\t\t{\n\t\t\t\tbyte secondByte = bytes[startIndex + 1];\n\t\t\t\tif( secondByte == 0xDA )\n\t\t\t\t{\n\t\t\t\t\tstartIndex = -1;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telse if( secondByte == 0x01 || ( secondByte >= 0xD0 && secondByte <= 0xD9 ) )\n\t\t\t\t{\n\t\t\t\t\tstartIndex += 2;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tchunkSize = bytes[startIndex + 2] * 256 + bytes[startIndex + 3];\n\n\t\t\t\t\tif( secondByte == 0xE1 && chunkSize >= 31 && CheckBytesForXMPNamespace_JPEG( bytes, startIndex + 4 ) )\n\t\t\t\t\t{\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tstartIndex = startIndex + 2 + chunkSize;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate static int FindIndexToInsertXMPCode_JPEG( byte[] bytes )\n\t{\n\t\tint chunkSize = bytes[4] * 256 + bytes[5];\n\t\treturn chunkSize + 4;\n\t}\n\t#endregion\n\n\t#region PNG Encoding\n\tprivate static byte[] DoTheHardWork_PNG( byte[] fileBytes, int imageWidth, int imageHeight )\n\t{\n\t\tstring xmpContent = \"iTXt\" + string.Format( XMP_CONTENT_TO_FORMAT_PNG, 75f.ToString( \"F1\" ), imageWidth, imageHeight );\n\t\tint copyBytesUntil = 33;\n\t\tint xmpLength = xmpContent.Length - 4; // minus iTXt\n\t\tstring xmpCRC = CalculateCRC_PNG( xmpContent );\n\t\txmpContent = string.Concat( (char) ( xmpLength >> 24 ), (char) ( xmpLength >> 16 ), (char) ( xmpLength >> 8 ), (char) ( xmpLength ),\n\t\t\t\t\t\t\t\t\txmpContent, xmpCRC );\n\n\t\tbyte[] result = new byte[fileBytes.Length + xmpContent.Length];\n\n\t\tArray.Copy( fileBytes, 0, result, 0, copyBytesUntil );\n\n\t\tfor( int i = 0; i < xmpContent.Length; i++ )\n\t\t{\n\t\t\tresult[copyBytesUntil + i] = (byte) xmpContent[i];\n\t\t}\n\n\t\tArray.Copy( fileBytes, copyBytesUntil, result, copyBytesUntil + xmpContent.Length, fileBytes.Length - copyBytesUntil );\n\n\t\treturn result;\n\t}\n\n\t// Source: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/Security/Cryptography/Crc32.cs\n\tprivate static string CalculateCRC_PNG( string xmpContent )\n\t{\n\t\tif( CRC_TABLE_PNG == null )\n\t\t\tCalculateCRCTable_PNG();\n\n\t\tuint crc = ~UpdateCRC_PNG( xmpContent );\n\t\tbyte[] crcBytes = CalculateCRCBytes_PNG( crc );\n\n\t\treturn string.Concat( (char) crcBytes[0], (char) crcBytes[1], (char) crcBytes[2], (char) crcBytes[3] );\n\t}\n\n\tprivate static uint UpdateCRC_PNG( string xmpContent )\n\t{\n\t\tuint c = 0xFFFFFFFF;\n\t\tfor( int i = 0; i < xmpContent.Length; i++ )\n\t\t{\n\t\t\tc = ( c >> 8 ) ^ CRC_TABLE_PNG[xmpContent[i] ^ c & 0xFF];\n\t\t}\n\n\t\treturn c;\n\t}\n\n\tprivate static void CalculateCRCTable_PNG()\n\t{\n\t\tCRC_TABLE_PNG = new uint[256];\n\t\tfor( uint i = 0; i < 256; i++ )\n\t\t{\n\t\t\tuint c = i;\n\t\t\tfor( int j = 0; j < 8; j++ )\n\t\t\t{\n\t\t\t\tif( ( c & 1 ) == 1 )\n\t\t\t\t\tc = ( c >> 1 ) ^ 0xEDB88320;\n\t\t\t\telse\n\t\t\t\t\tc = ( c >> 1 );\n\t\t\t}\n\n\t\t\tCRC_TABLE_PNG[i] = c;\n\t\t}\n\t}\n\n\tprivate static byte[] CalculateCRCBytes_PNG( uint crc )\n\t{\n\t\tvar result = BitConverter.GetBytes( crc );\n\n\t\tif( BitConverter.IsLittleEndian )\n\t\t\tArray.Reverse( result );\n\n\t\treturn result;\n\t}\n\t#endregion\n\t#endregion\n}"
  },
  {
    "path": "Plugins/Simple360Render/I360Render.cs.meta",
    "content": "fileFormatVersion: 2\nguid: 801b5200ce5a97c45be851954572218b\ntimeCreated: 1498249486\nlicenseType: Free\nMonoImporter:\n  serializedVersion: 2\n  defaultReferences: []\n  executionOrder: 0\n  icon: {instanceID: 0}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render/README.txt",
    "content": "= 360° Screenshot Capture (v1.1.1) =\n\nDocumentation: https://github.com/yasirkula/Unity360ScreenshotCapture\nFAQ: https://github.com/yasirkula/Unity360ScreenshotCapture#faq\nExample code: https://github.com/yasirkula/Unity360ScreenshotCapture#example-code\nE-mail: yasirkula@gmail.com"
  },
  {
    "path": "Plugins/Simple360Render/README.txt.meta",
    "content": "fileFormatVersion: 2\nguid: 639da580aad6c4d4fb7e979a1f626fa6\ntimeCreated: 1563308755\nlicenseType: Free\nTextScriptImporter:\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render/Resources/EquirectangularConverter.shader",
    "content": "﻿// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'\n\n// Credit: https://github.com/Mapiarz/CubemapToEquirectangular/blob/master/Assets/Shaders/CubemapToEquirectangular.shader\n\nShader \"Hidden/I360CubemapToEquirectangular\"\n{\n\tProperties\n\t{\n\t\t_MainTex (\"Cubemap (RGB)\", CUBE) = \"\" {}\n\t\t_PaddingX (\"Padding X\", Float) = 0.0\n\t}\n\n\tSubshader\n\t{\n\t\tPass\n\t\t{\n\t\t\tZTest Always Cull Off ZWrite Off\n\n\t\t\tCGPROGRAM\n\t\t\t\t#pragma vertex vert\n\t\t\t\t#pragma fragment frag\n\t\t\t\t#pragma fragmentoption ARB_precision_hint_fastest\n\t\t\t\t//#pragma fragmentoption ARB_precision_hint_nicest\n\t\t\t\t#include \"UnityCG.cginc\"\n\n\t\t\t\t#define PI    3.141592653589793\n\t\t\t\t#define TWOPI 6.283185307179587\n\n\t\t\t\tstruct v2f\n\t\t\t\t{\n\t\t\t\t\tfloat4 pos : POSITION;\n\t\t\t\t\tfloat2 uv : TEXCOORD0;\n\t\t\t\t};\n\t\t\n\t\t\t\tsamplerCUBE _MainTex;\n\t\t\t\tfloat _PaddingX;\n\t\t\t\t\n\t\t\t\tv2f vert(appdata_img v)\n\t\t\t\t{\n\t\t\t\t\tv2f o;\n\t\t\t\t\to.pos = UnityObjectToClipPos(v.vertex);\n\t\t\t\t\to.uv = (v.texcoord.xy + float2(_PaddingX,0)) * float2(TWOPI, PI);\n\t\t\t\t\treturn o;\n\t\t\t\t}\n\t\t\n\t\t\t\tfixed4 frag(v2f i) : COLOR \n\t\t\t\t{\n\t\t\t\t\tfloat theta = i.uv.y;\n\t\t\t\t\tfloat phi = i.uv.x;\n\t\t\t\t\tfloat3 unit = float3(0,0,0);\n\n\t\t\t\t\tunit.x = sin(phi) * sin(theta) * -1;\n\t\t\t\t\tunit.y = cos(theta) * -1;\n\t\t\t\t\tunit.z = cos(phi) * sin(theta) * -1;\n\n\t\t\t\t\treturn texCUBE(_MainTex, unit);\n\t\t\t\t}\n\t\t\tENDCG\n\t\t}\n\t}\n\tFallback Off\n}"
  },
  {
    "path": "Plugins/Simple360Render/Resources/EquirectangularConverter.shader.meta",
    "content": "fileFormatVersion: 2\nguid: 73817e9d29a0033499fbf28be55f2120\ntimeCreated: 1498250385\nlicenseType: Free\nShaderImporter:\n  defaultTextures: []\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render/Resources.meta",
    "content": "fileFormatVersion: 2\nguid: 5d84eb59dd45e4c45952358448ed5279\nfolderAsset: yes\ntimeCreated: 1498250425\nlicenseType: Free\nDefaultImporter:\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render/Simple360Render.Runtime.asmdef",
    "content": "﻿{\n\t\"name\": \"Simple360Render.Runtime\"\n}\n"
  },
  {
    "path": "Plugins/Simple360Render/Simple360Render.Runtime.asmdef.meta",
    "content": "fileFormatVersion: 2\nguid: 786f92cee518bdc448b345723f72322e\nAssemblyDefinitionImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins/Simple360Render.meta",
    "content": "fileFormatVersion: 2\nguid: 161d6e8d9f8984c4a99d559eba29d660\nfolderAsset: yes\ntimeCreated: 1498250360\nlicenseType: Free\nDefaultImporter:\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "Plugins.meta",
    "content": "fileFormatVersion: 2\nguid: 07bf4b39630a4274a8b76326493f09a6\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"com.yasirkula.screenshotcapture\",\n  \"displayName\": \"360\\u00b0 Screenshot Capture\",\n  \"version\": \"1.1.1\",\n  \"documentationUrl\": \"https://github.com/yasirkula/Unity360ScreenshotCapture\",\n  \"changelogUrl\": \"https://github.com/yasirkula/Unity360ScreenshotCapture/releases\",\n  \"licensesUrl\": \"https://github.com/yasirkula/Unity360ScreenshotCapture/blob/master/LICENSE.txt\",\n  \"description\": \"This plugin helps you capture 360\\u00b0 screenshots in equirectangular format during gameplay.\"\n}\n"
  },
  {
    "path": "package.json.meta",
    "content": "fileFormatVersion: 2\nguid: 6ba4c2542477a1c4f9d7150c62af4bf2\nPackageManifestImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  }
]