/// <summary>
        /// Gets Unity Texture2D from Daggerfall texture with more options.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="record">Record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <param name="alphaIndex">Index to receive transparent alpha.</param>
        /// <param name="rectOut">Receives UV rect for texture inside border.</param>
        /// <param name="border">Number of pixels border to add around image.</param>
        /// <param name="dilate">Blend texture into surrounding empty pixels. Requires border.</param>
        /// <param name="copyToOppositeBorder">Copy texture edges to opposite border. Requires border, will overwrite dilate.</param>
        /// <returns>Texture2D or null.</returns>
        public Texture2D GetTexture2D(
            int archive,
            int record,
            int frame,
            int alphaIndex,
            out Rect rectOut,
            int border  = 0,
            bool dilate = false,
            bool copyToOppositeBorder = false,
            bool makeNoLongerReadable = true)
        {
            // Ready check
            if (!ReadyCheck())
            {
                rectOut = new Rect();
                return(null);
            }

            // Load texture file
            textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true);

            // Get Color32 array
            DFSize sz;

            Color32[] colors = textureFile.GetColors32(record, frame, alphaIndex, border, out sz);

            // Dilate edges
            if (border > 0 && dilate && !copyToOppositeBorder)
            {
                ImageProcessing.DilateColors(ref colors, sz);
            }

            // Copy to opposite border
            if (border > 0 && copyToOppositeBorder)
            {
                ImageProcessing.WrapBorder(ref colors, sz, border);
            }

            // Create Texture2D
            Texture2D texture;

            if (alphaIndex < 0)
            {
                texture = new Texture2D(sz.Width, sz.Height, TextureFormat.RGB24, MipMaps);
            }
            else
            {
                texture = new Texture2D(sz.Width, sz.Height, TextureFormat.RGBA32, MipMaps);
            }
            texture.SetPixels32(colors);
            texture.Apply(true, makeNoLongerReadable);

            // Shrink UV rect to compensate for internal border
            float ru = 1f / sz.Width;
            float rv = 1f / sz.Height;

            rectOut = new Rect(border * ru, border * rv, (sz.Width - border * 2) * ru, (sz.Height - border * 2) * rv);

            return(texture);
        }
        /// <summary>
        /// Gets specially packed tileset atlas for terrains.
        /// This needs to be improved to create each mip level manually to further reduce artifacts.
        /// </summary>
        /// <param name="archive">Archive index.</param>
        /// <param name="stayReadable">Texture should stay readable.</param>
        /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param>
        /// <returns></returns>
        public GetTextureResults GetTerrainTilesetTexture(
            int archive,
            bool stayReadable = false,
            SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24)
        {
            const int atlasDim   = 2048;
            const int gutterSize = 32;

            GetTextureResults results = new GetTextureResults();

            // Load texture file and check count matches terrain tiles
            TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true);

            if (textureFile.RecordCount != 56)
            {
                return(results);
            }

            // Rollout tiles into atlas.
            // This is somewhat inefficient, but fortunately atlases only
            // need to be generated once and can be prepared offline where
            // startup time is critical.
            int x = 0, y = 0;

            Color32[] atlasColors = new Color32[atlasDim * atlasDim];
            for (int record = 0; record < textureFile.RecordCount; record++)
            {
                // Create base image with gutter
                DFSize    sz;
                Color32[] albedo = textureFile.GetColors32(record, 0, -1, gutterSize, out sz);

                // Wrap and clamp textures based on tile
                switch (record)
                {
                // Textures that do not tile in any direction
                case 5:
                case 7:
                case 10:
                case 12:
                case 15:
                case 17:
                case 20:
                case 22:
                case 25:
                case 27:
                case 30:
                case 32:
                case 34:
                case 35:
                case 36:
                case 37:
                case 38:
                case 39:
                case 40:
                case 41:
                case 42:
                case 43:
                case 44:
                case 45:
                case 47:
                case 48:
                case 49:
                case 50:
                case 51:
                case 52:
                case 53:
                case 55:
                    ImageProcessing.ClampBorder(ref albedo, sz, gutterSize);
                    break;

                // Textures that clamp horizontally and tile vertically
                case 6:
                case 11:
                case 16:
                case 21:
                case 26:
                case 31:
                    ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false);
                    ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false);
                    break;

                // Textures that tile in all directions
                default:
                    ImageProcessing.WrapBorder(ref albedo, sz, gutterSize);
                    break;
                }

                // Create variants
                Color32[] rotate     = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height);
                Color32[] flip       = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height);
                Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height);

                // Insert into atlas
                ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim);
                ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim);

                // Increment position
                x += sz.Width * 4;
                if (x >= atlasDim)
                {
                    y += sz.Height;
                    x  = 0;
                }
            }

            // Create Texture2D
            Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps);

            albedoAtlas.SetPixels32(atlasColors);
            albedoAtlas.Apply(true, !stayReadable);

            // Change settings for these textures
            albedoAtlas.wrapMode   = TextureWrapMode.Clamp;
            albedoAtlas.anisoLevel = 8;

            // Store results
            results.albedoMap = albedoAtlas;

            return(results);
        }