コード例 #1
0
ファイル: SkiaRenderHandler.cs プロジェクト: vvvv/VL.CEF
 public void Dispose()
 {
     rasterImage?.Dispose();
     textureImage?.Dispose();
     sharedTexture?.Dispose();
     renderContext.Dispose();
 }
コード例 #2
0
        private void OnPaintSample(object sender, SKPaintSurfaceEventArgs e)
        {
            Sample?.DrawSample(e.Surface.Canvas, e.Info.Width, e.Info.Height);

            lastImage?.Dispose();
            lastImage = e.Surface.Snapshot();

            var view = sender as SKCanvasView;

            DrawScaling(view, e.Surface.Canvas, view.CanvasSize);
        }
コード例 #3
0
        public override void Dispose()
        {
            if (_skiaCanvas != null)
            {
                _skiaCanvas.Dispose();
                _skiaCanvas = null;
            }

            if (_surface != null)
            {
                _surface.Dispose();
                _surface = null;
            }

            if (_image != null)
            {
                _image.Dispose();
                _image = null;
            }

            if (_bitmap != null && _disposeBitmap)
            {
                _bitmap.Dispose();
                _bitmap = null;
            }

            _canvas = null;

            base.Dispose();
        }
コード例 #4
0
        /// <summary>
        /// Tries to add an element to the current element, from the right.
        /// </summary>
        /// <param name="Element">Element to add.</param>
        /// <returns>Result, if understood, null otherwise.</returns>
        public override ISemiGroupElement AddRight(ISemiGroupElement Element)
        {
            if (!(Element is Graph G))
            {
                return(null);
            }

            GraphSettings Settings = new GraphSettings()
            {
                Width  = this.width,
                Height = this.height
            };

            SKImage Bmp = G.CreateBitmap(Settings);

            if (this.bitmap is null)
            {
                return(new GraphBitmap(Bmp));
            }

            using (SKSurface Surface = SKSurface.Create(new SKImageInfo(Math.Max(Bmp.Width, this.width),
                                                                        Math.Max(Bmp.Height, this.height), SKImageInfo.PlatformColorType, SKAlphaType.Premul)))
            {
                SKCanvas Canvas = Surface.Canvas;

                Canvas.DrawImage(this.bitmap, 0, 0);
                Canvas.DrawImage(Bmp, 0, 0);

                Bmp.Dispose();

                return(new GraphBitmap(Surface.Snapshot()));
            }
        }
コード例 #5
0
ファイル: SkiaImage.cs プロジェクト: ebatianoSoftware/CrossX
 protected override void Dispose(bool disposing)
 {
     if (skShader != null && skShader.Handle != IntPtr.Zero)
     {
         skShader.Dispose();
     }
     SKImage.Dispose();
 }
コード例 #6
0
 //Dispose everything to not get memory exceptions
 protected override void OnClosing(CancelEventArgs e)
 {
     _rasterImg.Dispose();
     _surface.Dispose();
     foreach (var img in _textureImages)
     {
         img.Dispose();
     }
     base.OnClosing(e);
 }
コード例 #7
0
        public void Dispose()
        {
            _bitmap?.Dispose();
            _codec?.Dispose();
            _image?.Dispose();

            if (_disposeStream)
            {
                _stream?.Dispose();
            }
        }
コード例 #8
0
        private async void OnPickImage(object sender, EventArgs e)
        {
            pickedImage?.Dispose();
            pickedImage = null;

            var picked = await CrossFilePicker.Current.PickFile();

            pickedImage = SKImage.FromEncodedData(picked.GetStream());

            skiaView.InvalidateSurface();
        }
コード例 #9
0
 private void RenderImage(SKCanvas canvas)
 {
     if (Image == null)
     {
         return;
     }
     if (_lastClipWidth != canvas.ClipDeviceBounds.Width || _scaledImage == null)
     {
         _scaledImage?.Dispose();
         _scaledImage   = ResizeImage(canvas.ClipDeviceBounds, Image.Data);
         _lastClipWidth = canvas.ClipDeviceBounds.Width;
     }
     canvas.DrawImage(_scaledImage, 0, 0);
 }
コード例 #10
0
        public Stream Assemble()
        {
            // # Set the number of frames per second to display
            // frameRate( frame_rate )

            //SKCanvas canvas = new SKCanvas()
            SKPath  path    = new SKPath();
            Stream  stream  = null;
            SKImage skImage = null;

            try {
                using (var skSurface = SKSurface.Create(new SKImageInfo(w, h))) {
                    var canvas = skSurface.Canvas;
                    // # Sets color space to Hue Saturation Brightness with max values of HSB respectively
                    // colorMode(HSB, 360, 100, 100, 100)

                    //background(0, 0, 100)
                    canvas.Clear(SKColors.White);
                    // rectMode(CORNER)

                    // draw ...
                    draw(canvas);

                    canvas.Flush();

                    //skImage = skSurface.Snapshot();
                    SKPaint paintConvert = SkiaSharpUtility.CreateDefaultPaint();
                    //skImage = SkiaSharpUtility.ScaleSurfaceToImage( skSurface, ImageCanvasSize, size, paintConvert );
                    skImage = skSurface.Snapshot();
                }
                // encode
                SKData skData = SkiaSharpUtility.EncodeImageToSKData(skImage, "png");

                stream = skData.AsStream();
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
            finally {
                if (skImage != null)
                {
                    skImage.Dispose();
                }
                paint.Dispose();
            }
            return(stream);
        }
コード例 #11
0
        private void MergeButtonOnClick(object sender, EventArgs e)
        {
            if (_image == null)
            {
                return;
            }

            var left = ProjectFisheye(new SKPoint(480, 480), 480, (float)Math.PI / 2);  // 90 degrees clockwise THETA S assumption

            using (var data = left.Encode(SKEncodedImageFormat.Jpeg, 80))
                using (var stream = File.OpenWrite(@"C:\users\jlange\desktop\left.jpg"))
                {
                    data.SaveTo(stream);
                }

            var right = ProjectFisheye(new SKPoint(1440, 480), 480, (float)-Math.PI / 2);  // -90 degress clockwise

            using (var data = right.Encode(SKEncodedImageFormat.Jpeg, 100))
                using (var stream = File.OpenWrite(@"C:\users\jlange\desktop\right.jpg"))
                {
                    data.SaveTo(stream);
                }

            var mergedProjection = SKSurface.Create(new SKImageInfo(960, 480));

            mergedProjection.Canvas.DrawImage(left, new SKRect(0, 0, 480, 480), new SKRect(480, 0, 960, 480));
            mergedProjection.Canvas.DrawImage(right, new SKRect(0, 0, 480, 480), new SKRect(0, 0, 480, 480));

            _image.Dispose();
            _image = mergedProjection.Snapshot();

            using (var data = _image.Encode(SKEncodedImageFormat.Jpeg, 100))
                using (var stream = File.OpenWrite(@"C:\users\jlange\desktop\merged.jpg"))
                {
                    data.SaveTo(stream);
                }

            _skGlControl.Invalidate();
        }
コード例 #12
0
        public Stream Assemble(int size = 256)
        {
            //SKCanvas canvas = new SKCanvas()
            SKPath  path    = new SKPath();
            Stream  stream  = null;
            SKImage skImage = null;

            try {
                using (var skSurface = SKSurface.Create(new SKImageInfo(w, h))) {
                    var canvas = skSurface.Canvas;
                    canvas.Clear(bc); //set background color
                    // draw ...
                    draw(canvas);

                    canvas.Flush();

                    //skImage = skSurface.Snapshot();
                    SKPaint paintConvert = SkiaSharpUtility.CreateDefaultPaint();
                    skImage = SkiaSharpUtility.CropSurfaceToImage(skSurface, ImageCanvasSize, size, paintConvert);
                }
                // encode
                SKData skData = SkiaSharpUtility.EncodeImageToSKData(skImage, "png");

                stream = skData.AsStream();
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
            finally {
                if (skImage != null)
                {
                    skImage.Dispose();
                }
                paint.Dispose();
            }
            return(stream);
        }
コード例 #13
0
        static void HandleCaptcha(string Original, string Output, int Threshold, int MaxBlot)
        {
            // Loading image using path
            SKBitmap bitmap = SKBitmap.Decode(Original);

            // Converting to red/white format
            for (int i = 0; i < bitmap.Width; i++)
            {
                for (int j = 0; j < bitmap.Height; j++)
                {
                    if (bitmap.GetPixel(i, j).Red > Threshold)
                    {
                        bitmap.SetPixel(i, j, SKColors.White);
                    }
                    else
                    {
                        bitmap.SetPixel(i, j, SKColors.Black);
                    }
                }
            }
            // Cleaning
            List <Point> points = new List <Point>(); // Current blot points
            List <Point> digits = new List <Point>(); // All digits points
            int          oldReviewPointsCount;

            for (int i = 0; i < bitmap.Width; i++)
            {
                for (int j = 0; j < bitmap.Height; j++)
                {
                    if (bitmap.GetPixel(i, j) == SKColors.Black && !digits.Contains(new Point(i, j)))
                    {
                        points.Clear();
                        points.Add(new Point(i, j));
                        ExploreBlot();
                        if (points.Count <= MaxBlot)
                        {
                            ClearBlot();
                        }
                        else
                        {
                            digits.AddRange(points);
                        }
                    }
                }
            }
            // Saving
            SKImage    image  = SKImage.FromBitmap(bitmap);
            SKData     data   = image.Encode(SKEncodedImageFormat.Jpeg, 100);
            FileStream stream = new FileStream(Output, FileMode.Create, FileAccess.Write);

            data.SaveTo(stream);
            stream.Close();
            // Resources release
            stream.Dispose();
            data.Dispose();
            image.Dispose();
            bitmap.Dispose();
            // The end
            Console.Write("The work has been completed. Press any key to exit..");
            Console.ReadKey(true);

            void ExploreBlot()
            {
                oldReviewPointsCount = points.Count;
                List <Point> staticPoints = new List <Point>(points);

                for (int i = 0; i < staticPoints.Count; i++)
                {
                    Point a = new Point(staticPoints[i].X, staticPoints[i].Y);
                    if (bitmap.GetPixel(a.X - 1, a.Y - 1) == SKColors.Black && !points.Contains(new Point(a.X - 1, a.Y - 1)))
                    {
                        points.Add(new Point(a.X - 1, a.Y - 1));
                    }
                    if (bitmap.GetPixel(a.X, a.Y - 1) == SKColors.Black && !points.Contains(new Point(a.X, a.Y - 1)))
                    {
                        points.Add(new Point(a.X, a.Y - 1));
                    }
                    if (bitmap.GetPixel(a.X + 1, a.Y - 1) == SKColors.Black && !points.Contains(new Point(a.X + 1, a.Y - 1)))
                    {
                        points.Add(new Point(a.X + 1, a.Y - 1));
                    }
                    if (bitmap.GetPixel(a.X - 1, a.Y) == SKColors.Black && !points.Contains(new Point(a.X - 1, a.Y)))
                    {
                        points.Add(new Point(a.X - 1, a.Y));
                    }
                    if (bitmap.GetPixel(a.X + 1, a.Y) == SKColors.Black && !points.Contains(new Point(a.X + 1, a.Y)))
                    {
                        points.Add(new Point(a.X + 1, a.Y));
                    }
                    if (bitmap.GetPixel(a.X - 1, a.Y + 1) == SKColors.Black && !points.Contains(new Point(a.X - 1, a.Y + 1)))
                    {
                        points.Add(new Point(a.X - 1, a.Y + 1));
                    }
                    if (bitmap.GetPixel(a.X, a.Y + 1) == SKColors.Black && !points.Contains(new Point(a.X, a.Y + 1)))
                    {
                        points.Add(new Point(a.X, a.Y + 1));
                    }
                    if (bitmap.GetPixel(a.X + 1, a.Y + 1) == SKColors.Black && !points.Contains(new Point(a.X + 1, a.Y + 1)))
                    {
                        points.Add(new Point(a.X + 1, a.Y + 1));
                    }
                }
                if (points.Count != oldReviewPointsCount)
                {
                    ExploreBlot();
                }
            }

            void ClearBlot()
            {
                for (int i = 0; i < points.Count; i++)
                {
                    bitmap.SetPixel(points[i].X, points[i].Y, SKColors.White);
                }
            }
        }
コード例 #14
0
 /// <inheritdoc />
 public void Dispose()
 {
     _image.Dispose();
 }
コード例 #15
0
        public Stream Assemble(string roboset, string color, string format, string bgset, int size = 256)
        {
            if (size < 256)
            {
                _ = 256;
            }
            else if (size > ImageCanvasSize)
            {
                size = ImageCanvasSize;
            }
            // roboset
            if (roboset == "any")
            {
                // roboset = self.sets[self.hasharray[1] % len(self.sets) ]
                int index = (int)(this.hashes[1] % this.sets.Count);
                _ = this.sets[index];
            }
            else if (!this.sets.Contains(roboset))
            {
                _ = this.sets[0];
            }

            /*
             # Only set1 is setup to be color-seletable. The others don't have enough pieces in various colors.
             # This could/should probably be expanded at some point..
             # Right now, this feature is almost never used. ( It was < 44 requests this year, out of 78M reqs )
             */
            if (roboset == "set1")
            {
                if (!string.IsNullOrEmpty(color) && this.colorsInSet1.Contains(color))
                {
                    roboset = "set1\\" + color;
                }
                else
                {
                    //randomcolor = self.colors[self.hasharray[0] % len(self.colors) ]
                    int index = (int)(this.hashes[0] % this.colorsInSet1.Count);
                    roboset = "set1\\" + this.colorsInSet1[index];
                }
            } // roboset == "set1"

            // If they specified a background, ensure it's legal, then give it to them.
            if (!bgSets.Contains(bgset) || bgset == "any")
            {
                //bgset = self.bgsets[self.hasharray[2] % len( self.bgsets )]
                int index = (int)(this.hashes[2] % this.bgSets.Count);
                bgset = this.bgSets[index];
            }

            // If we set a format based on extension earlier, use that. Otherwise, PNG.
            if (string.IsNullOrEmpty(format))
            {
                format = this.format;
            }

            /*
             # Each directory in our set represents one piece of the Robot, such as the eyes, nose, mouth, etc.
             #
             # Each directory is named with two numbers - The number before the # is the sort order.
             # This ensures that they always go in the same order when choosing pieces, regardless of OS.
             #
             # The second number is the order in which to apply the pieces.
             # For instance, the head has to go down BEFORE the eyes, or the eyes would be hidden.
             #
             # First, we'll get a list of parts of our robot.
             */
            var roboparts = getImagesPaths(this.resourceDir + "sets\\" + roboset);

            // Now that we've sorted them by the first number, we need to sort each sub-category by the second.
            roboparts = roboparts
                        .OrderBy(s => s.Split('#')[1])
                        .ToList();

            Stream  stream  = null;
            SKImage skImage = null;
            SKPaint paint   = Tools.SkiaSharpUtility.CreateDefaultPaint();

            try {
                using (var skSurface = SKSurface.Create(new SKImageInfo(ImageCanvasSize, ImageCanvasSize))) {
                    var canvas = skSurface.Canvas;
                    canvas.Clear(SKColors.Transparent); //set background color
                    // draw background
                    if (!string.IsNullOrEmpty(bgset))
                    {
                        string        bgDirPath         = this.resourceDir + "backgrounds\\" + bgset;
                        List <string> legalBackgrounds  = new List <string>();
                        var           backgrounds       = System.IO.Directory.GetFiles(bgDirPath);
                        var           sortedBackgrounds = backgrounds.OrderBy(s => s);
                        foreach (string bg in sortedBackgrounds)
                        {
                            string bgName = bg.Substring(bgDirPath.Length);
                            if (!bgName.StartsWith(".")) // skip files like .git, .xxx
                            {
                                legalBackgrounds.Add(bg);
                            }
                        }
                        // Use some of our hash bits to choose which file
                        int    index      = (int)(this.hashes[3] % legalBackgrounds.Count);
                        string bgPartPath = legalBackgrounds[index];
                        drawToCanvas(canvas, bgPartPath, paint);
                    }
                    foreach (var partImage in roboparts)
                    {
                        drawToCanvas(canvas, partImage, paint);
                    }
                    canvas.Flush();

                    skImage = Tools.SkiaSharpUtility.ScaleSurfaceToImage(skSurface, ImageCanvasSize, size, paint);
                }
                // encode
                SKData skData = Tools.SkiaSharpUtility.EncodeImageToSKData(skImage, format);

                stream = skData.AsStream();
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
            finally {
                if (skImage != null)
                {
                    skImage.Dispose();
                }
                paint.Dispose();
            }
            return(stream);
        }
コード例 #16
0
        public Stream Combine(IEnumerable <Stream> files)
        {
            SKImage finalImage = null;

            //read all images into memory
            List <SKBitmap> images = new List <SKBitmap>();

            try
            {
                int width  = 0;
                int height = 0;

                foreach (var file in files)
                {
                    //create a bitmap from the file and add it to the list
                    SKBitmap bitmap = SKBitmap.Decode(file);

                    //update the size of the final bitmap
                    width  = bitmap.Width;
                    height = bitmap.Height;

                    images.Add(bitmap);
                }

                //get a surface so we can draw an image
                using (var tempSurface = SKSurface.Create(new SKImageInfo(width, height)))
                {
                    //get the drawing canvas of the surface
                    var canvas = tempSurface.Canvas;

                    //set background color
                    canvas.Clear(SKColors.Transparent);

                    //go through each image and draw it on the final image
                    int offset    = 0;
                    int offsetTop = 0;
                    foreach (SKBitmap image in images)
                    {
                        canvas.DrawBitmap(image, SKRect.Create(offset, offsetTop, image.Width, image.Height));
                        //offsetTop = offsetTop > 0 ? 0 : image.Height / 2;
                        //offset += (int)(image.Width / 1.6);
                    }

                    // return the surface as a manageable image
                    finalImage = tempSurface.Snapshot();
                }

                //using (SKData encoded = finalImage.Encode(SKEncodedImageFormat.Png, 100))
                //using (Stream outFile = File.OpenWrite("stitchedImage.png"))
                //{
                //    encoded.SaveTo(ms);
                //}

                var ms = new MemoryStream();
                finalImage.Encode(SKEncodedImageFormat.Png, 75).SaveTo(ms);
                ms.Position = 0;
                return(ms);
                //return the image that was just drawn
            }
            finally
            {
                //clean up memory
                foreach (SKBitmap image in images)
                {
                    image.Dispose();
                }

                finalImage?.Dispose();
            }
        }
コード例 #17
0
        private SKData GetImageData(string imagePath, ResizeParams resizeParams, DateTime lastWriteTimeUtc)
        {
            // check cache and return if cached
            string cacheKey;

            unchecked
            {
                cacheKey = "imgasc_" + imagePath.GetHashCode() + lastWriteTimeUtc.ToBinary() + resizeParams.ToString().GetHashCode();
            }

            SKData imageData;

            byte[] cacheData = _asyncCacheService.GetByKeyAsync <byte[]>(cacheKey).Result;
            if (cacheData != null)
            {
                return(SKData.CreateCopy(cacheData));
            }

            // this represents the EXIF orientation
            SKBitmap bitmap = LoadBitmap(File.OpenRead(imagePath), out SKEncodedOrigin origin); // always load as 32bit (to overcome issues with indexed color)

            // if autorotate = true, and origin isn't correct for the rotation, rotate it
            if (resizeParams.autorotate && origin != SKEncodedOrigin.TopLeft)
            {
                bitmap = RotateAndFlip(bitmap, origin);
            }

            // if either w or h is 0, set it based on ratio of original image
            if (resizeParams.h == 0)
            {
                resizeParams.h = (int)Math.Round(bitmap.Height * (float)resizeParams.w / bitmap.Width);
            }
            else if (resizeParams.w == 0)
            {
                resizeParams.w = (int)Math.Round(bitmap.Width * (float)resizeParams.h / bitmap.Height);
            }

            // if we need to crop, crop the original before resizing
            if (resizeParams.mode == "crop")
            {
                bitmap = Crop(bitmap, resizeParams);
            }

            // store padded height and width
            int paddedHeight = resizeParams.h;
            int paddedWidth  = resizeParams.w;

            // if we need to pad, or max, set the height or width according to ratio
            if (resizeParams.mode == "pad" || resizeParams.mode == "max")
            {
                float bitmapRatio = (float)bitmap.Width / bitmap.Height;
                float resizeRatio = (float)resizeParams.w / resizeParams.h;

                if (bitmapRatio > resizeRatio) // original is more "landscape"
                {
                    resizeParams.h = (int)Math.Round(bitmap.Height * ((float)resizeParams.w / bitmap.Width));
                }
                else
                {
                    resizeParams.w = (int)Math.Round(bitmap.Width * ((float)resizeParams.h / bitmap.Height));
                }
            }

            // resize
            SKImageInfo resizedImageInfo = new SKImageInfo(resizeParams.w, resizeParams.h, SKImageInfo.PlatformColorType, bitmap.AlphaType);
            SKBitmap    resizedBitmap    = bitmap.Resize(resizedImageInfo, SKFilterQuality.None);

            // optionally pad
            if (resizeParams.mode == "pad")
            {
                resizedBitmap = Pad(resizedBitmap, paddedWidth, paddedHeight, resizeParams.format != "png");
            }

            // encode
            SKImage resizedImage = SKImage.FromBitmap(resizedBitmap);
            SKEncodedImageFormat encodeFormat = resizeParams.format == "png" ? SKEncodedImageFormat.Png : SKEncodedImageFormat.Jpeg;

            imageData = resizedImage.Encode(encodeFormat, resizeParams.quality);

            // cache the result
            _asyncCacheService.SetValueAsync(cacheKey, imageData.ToArray());

            // cleanup
            resizedImage.Dispose();
            bitmap.Dispose();
            resizedBitmap.Dispose();

            return(imageData);
        }
コード例 #18
0
 public void Dispose()
 {
     nativeSkBitmap?.Dispose();
     nativeSKImage?.Dispose();
 }
コード例 #19
0
 public override void Dispose()
 {
     _image.Dispose();
 }
コード例 #20
0
        static void HandleImage(string Original, string Output)
        {
            SKBitmap Picture = SKBitmap.Decode(Original);

            SKBitmap[] Digits        = new SKBitmap[4];
            SKBitmap[] CroppedDigits = new SKBitmap[4];
            SKImage    Image         = SKImage.FromBitmap(Picture);

            for (int i = 0; i < 4; i++)
            {
                Digits[i]        = SKBitmap.FromImage(Image.Subset(new SKRectI(i * Picture.Width / 4, 0, (i + 1) * Picture.Width / 4, Picture.Height)));
                CroppedDigits[i] = Crop(Digits[i]);
                SKData     data   = SKImage.FromBitmap(CroppedDigits[i]).Encode(SKEncodedImageFormat.Jpeg, 100);
                FileStream stream = new FileStream(Output + i.ToString() + ".jpg", FileMode.Create, FileAccess.Write);
                data.SaveTo(stream);
                stream.Close();
                stream.Dispose();
                data.Dispose();
                Digits[i].Dispose();
                CroppedDigits[i].Dispose();
            }
            Image.Dispose();
            Picture.Dispose();
            Console.Write("The work has been completed. Press any key to exit..");
            Console.ReadKey(true);

            SKBitmap Crop(SKBitmap bitmap)
            {
                int width = bitmap.Width, height = bitmap.Height;
                int Left = 0, Top = 0, Right = 0, Bottom = 0;

                // Left
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        if (bitmap.GetPixel(i, j) == SKColors.Black)
                        {
                            Left = i;
                            i    = width;
                            break;
                        }
                    }
                }
                // Top
                for (int i = 0; i < height; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        if (bitmap.GetPixel(j, i) == SKColors.Black)
                        {
                            Top = i;
                            i   = height;
                            break;
                        }
                    }
                }
                // Right
                for (int i = width; i > 0; i--)
                {
                    for (int j = 0; j < height; j++)
                    {
                        if (bitmap.GetPixel(i, j) == SKColors.Black)
                        {
                            Right = i;
                            i     = 0;
                            break;
                        }
                    }
                }
                // Bottom
                for (int i = height; i > 0; i--)
                {
                    for (int j = 0; j < width; j++)
                    {
                        if (bitmap.GetPixel(j, i) == SKColors.Black)
                        {
                            Bottom = i;
                            i      = 0;
                            break;
                        }
                    }
                }
                return(SKBitmap.FromImage(SKImage.FromBitmap(bitmap).Subset(new SKRectI(Left, Top, Right, Bottom))));
            }
        }