/// <summary>
        /// Grab a snapshot of the most recent image played.
        /// Returns A pointer to the raw pixel data.
        /// Caller must release this memory with Marshal.FreeCoTaskMem when it is no longer needed.
        /// </summary>
        /// <returns>A pointer to the raw pixel data</returns>
        private IntPtr GetCurrentFrame()
        {
            if (!m_bBufferSamplesOfCurrentFrame)
            {
                throw new Exception("SampleGrabberHelper was created without buffering-mode (buffer of current frame)");
            }

            int hr = 0;

            IntPtr ip        = IntPtr.Zero;
            int    iBuffSize = 0;

            // Read the buffer size
            hr = m_SampleGrabber.GetCurrentBuffer(ref iBuffSize, ip);
            DsError.ThrowExceptionForHR(hr);

            Debug.Assert(iBuffSize == m_ImageSize, "Unexpected buffer size");

            // Allocate the buffer and read it
            ip = Marshal.AllocCoTaskMem(iBuffSize);

            hr = m_SampleGrabber.GetCurrentBuffer(ref iBuffSize, ip);
            DsError.ThrowExceptionForHR(hr);

            return(ip);
        }
예제 #2
0
        /// <summary>
        /// Génère une image à partir de ce que contient le buffer du SampleGrabber.
        /// </summary>
        void GenerateImage()
        {
            int pBufferSize = 0;

            int hr = _sampleGrabber.GetCurrentBuffer(ref pBufferSize, IntPtr.Zero);

            DsError.ThrowExceptionForHR(hr);

            byte[]   byteArray   = new byte[pBufferSize];
            GCHandle pinnedArray = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
            IntPtr   pointer     = pinnedArray.AddrOfPinnedObject();

            hr = _sampleGrabber.GetCurrentBuffer(ref pBufferSize, pointer);
            DsError.ThrowExceptionForHR(hr);

            int stride = _videoWidth * 4;
            int scan0  = (int)pointer;

            scan0 += (_videoHeight - 1) * stride;


            Bitmap bitmapOriginalSize = new Bitmap(_videoWidth, _videoHeight, -stride, System.Drawing.Imaging.PixelFormat.Format32bppRgb, (IntPtr)scan0);

            bitmapOriginalSize = (Bitmap)bitmapOriginalSize.Clone(); // Permet d'avoir la même image mais avec un stride positif, qui est requis pour la conversion en System.Media.imaging.*

            _latestBitmapData = bitmapOriginalSize.LockBits(new Rectangle(0, 0, _videoWidth, _videoHeight),
                                                            ImageLockMode.ReadOnly,
                                                            System.Drawing.Imaging.PixelFormat.Format24bppRgb);


            pinnedArray.Free();

            _mre.Set();
        }
예제 #3
0
        //List<byte> cambytes = new List<byte>();

        private Bitmap GetBitmap(ISampleGrabber i_grabber, int width, int height, int stride)
        {
            // サンプルグラバから画像を取得するためには
            // まずサイズ0でGetCurrentBufferを呼び出しバッファサイズを取得し
            // バッファ確保して再度GetCurrentBufferを呼び出す。
            // 取得した画像は逆になっているので反転させる必要がある。
            int sz = 0;

            i_grabber.GetCurrentBuffer(ref sz, IntPtr.Zero); // IntPtr.Zeroで呼び出してバッファサイズ取得
            if (sz == 0)
            {
                return(null);
            }

            // メモリ確保し画像データ取得
            var ptr = Marshal.AllocCoTaskMem(sz);

            i_grabber.GetCurrentBuffer(ref sz, ptr);

            // 画像データをbyte配列に入れなおす
            var data = new byte[sz];

            Marshal.Copy(ptr, data, 0, sz);

            Bitmap result = null;

            try
            {
                // 画像を作成
                result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
                var bmp_data = result.LockBits(new Rectangle(Point.Empty, result.Size), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);


                //cambytes.AddRange(data);
                //Console.WriteLine(cambytes.Count);
                // 上下反転させながら1行ごとコピー
                for (int y = 0; y < height; y++)
                {
                    var src_idx = sz - (stride * (y + 1)); // 最終行から
                    //Console.WriteLine(src_idx);
                    var dst = new IntPtr(bmp_data.Scan0.ToInt32() + (stride * y));
                    Marshal.Copy(data, src_idx, dst, stride);
                    //
                    //Console.WriteLine(src_idx.ToString() + " data length");
                }

                result.UnlockBits(bmp_data);
                Marshal.FreeCoTaskMem(ptr);
            }
            catch (ArgumentException ax)
            {
                //System.Windows.Forms.MessageBox.Show(ax.Message);
                Console.WriteLine(ax.Message);
            }

            return(result);
        }
예제 #4
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="size"></param>
        /// <param name="pBuffer"></param>
        public void GetCurrentBuffer(ref int size, IntPtr pBuffer)
        {
            IntPtr pBuff = IntPtr.Zero;

            if (sampleGrabber != null)
            {
                //sampleGrabber.GetCurrentBuffer( ref size, pBuffer );
                sampleGrabber.GetCurrentBuffer(ref size, pBuff);
                pBuff = Marshal.AllocCoTaskMem(size);
                sampleGrabber.GetCurrentBuffer(ref size, pBuff);
                Marshal.FreeCoTaskMem(pBuff);
                //sampleGrabber.GetCurrentSample( out mediaSample );
            }
        }
예제 #5
0
        /// <summary>
        /// Gets the current frame from the buffer.
        /// </summary>
        /// <returns>The Bitmap of the frame.</returns>
        public Bitmap GetFrame()
        {
            if (_actualGraphState != GraphState.Rendered)
            {
                return(null);
            }

            //Asks for the buffer size.
            var bufferSize = 0;

            _sampGrabber.GetCurrentBuffer(ref bufferSize, IntPtr.Zero);

            if (bufferSize <= 0)
            {
                return(null);
            }

            if (_savedArray == null || _savedArray.Length < bufferSize)
            {
                _savedArray = new byte[bufferSize + 64000];
            }

            //Allocs the byte array.
            var handleObj = GCHandle.Alloc(_savedArray, GCHandleType.Pinned);

            //Gets the addres of the pinned object.
            var address = handleObj.AddrOfPinnedObject();

            try
            {
                //Puts the buffer inside the byte array.
                _sampGrabber.GetCurrentBuffer(ref bufferSize, address);

                //Image size.
                var width  = _videoInfoHeader.BmiHeader.Width;
                var height = _videoInfoHeader.BmiHeader.Height;

                var stride = width * 4;
                address += height * stride;

                return(new Bitmap(width, height, -stride,
                                  System.Drawing.Imaging.PixelFormat.Format32bppRgb,
                                  address));
            }
            finally
            {
                handleObj.Free();
            }
        }
예제 #6
0
        void TestSamples()
        {
            int          hr;
            int          iSize = 0;
            IMediaSample pSample;

            hr = m_isg.SetBufferSamples(true);
            DsError.ThrowExceptionForHR(hr);

            hr = m_imc.Run();
            Marshal.ThrowExceptionForHR(hr);

            // Wait a moment for the graph to start running
            Thread.Sleep(500);

            // Get a buffer (needs SetBufferSamples(true)
            hr = m_isg.GetCurrentBuffer(ref iSize, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);

            Debug.Assert(iSize > 0, "GetCurrentBuffer");

            hr = m_isg.GetCurrentSample(out pSample);
            // E_NOTIMPL - Nothing to test
            if (hr != -2147467263)
            {
                DsError.ThrowExceptionForHR(hr);
            }

            hr = m_imc.Stop();
            Marshal.ThrowExceptionForHR(hr);
        }
예제 #7
0
        public void tGetBitmap(Device device, CTexture ctex, int timeMs, bool bCopyToCTex = true)
        {
            int pBufferSize = 0;

            DsError.ThrowExceptionForHR(grabber.GetCurrentBuffer(ref pBufferSize, IntPtr.Zero));
            if (samplePtr == IntPtr.Zero)
            {
                samplePtr = Marshal.AllocHGlobal(pBufferSize);
            }
            if (bCopyToCTex && ctex != null)
            {
                DataRectangle dataRectangle = ctex.texture.LockRectangle(0, LockFlags.None);
                DsError.ThrowExceptionForHR(grabber.GetCurrentBuffer(ref pBufferSize, dataRectangle.DataPointer));
                ctex.texture.UnlockRectangle(0);
            }
        }
예제 #8
0
        /// <summary>
        /// Gets the current frame from the buffer.
        /// </summary>
        /// <returns>The Bitmap of the frame.</returns>
        public IDisposable GetFrame(IBitmapLoader BitmapLoader)
        {
            if (_actualGraphState != GraphState.Rendered)
            {
                return(null);
            }

            //Asks for the buffer size.
            var bufferSize = 0;

            _sampGrabber.GetCurrentBuffer(ref bufferSize, IntPtr.Zero);

            if (bufferSize <= 0)
            {
                return(null);
            }

            if (_savedArray == null || _savedArray.Length < bufferSize)
            {
                _savedArray = new byte[bufferSize + 64000];
            }

            //Allocs the byte array.
            var handleObj = GCHandle.Alloc(_savedArray, GCHandleType.Pinned);

            //Gets the addres of the pinned object.
            var address = handleObj.AddrOfPinnedObject();

            try
            {
                //Puts the buffer inside the byte array.
                _sampGrabber.GetCurrentBuffer(ref bufferSize, address);

                //Image size.
                var width  = _videoInfoHeader.BmiHeader.Width;
                var height = _videoInfoHeader.BmiHeader.Height;

                var stride = width * 4;
                address += height * stride;

                return(BitmapLoader.CreateBitmapBgr32(new Size(width, height), address, -stride));
            }
            finally
            {
                handleObj.Free();
            }
        }
예제 #9
0
        public unsafe void tGetBitmap(SharpDX.Direct3D9.Device device, CTexture ctex, int timeMs)
        {
            int bufferSize = 0;
            int hr         = 0x0;

            hr = grabber.GetCurrentBuffer(ref bufferSize, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);

            if (samplePtr == IntPtr.Zero)
            {
                samplePtr = Marshal.AllocHGlobal(bufferSize);
            }

            DataRectangle rectangle3 = ctex.texture.LockRectangle(0, SharpDX.Direct3D9.LockFlags.None);

            hr = grabber.GetCurrentBuffer(ref bufferSize, rectangle3.DataPointer);
            DsError.ThrowExceptionForHR(hr);
            ctex.texture.UnlockRectangle(0);
        }
예제 #10
0
        private void btnCapture_Click(object sender, EventArgs e)
        {
            isg.GetConnectedMediaType(ref SGMediaType);

            VIDEOINFOHEADER vih = (VIDEOINFOHEADER)Marshal.PtrToStructure(SGMediaType.pbFormat,
                                                                          typeof(VIDEOINFOHEADER));
            // Get a copy of the BITMAPINFOHEADER, to be used in the BITMAPFILEHEADER
            BITMAPINFOHEADER bih = vih.BitmapInfo;
            int len = (int)BitmapSize(bih);
            // Allocate bytes, plus room for a BitmapFileHeader
            int    sizeOfBFH = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
            int    sizeOfBIH = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
            IntPtr ptrBlock  = Marshal.AllocCoTaskMem(len + sizeOfBFH + sizeOfBIH);
            IntPtr ptrBIH    = new IntPtr(ptrBlock.ToInt64() + sizeOfBFH);
            IntPtr ptrImg    = new IntPtr(ptrBlock.ToInt64() + sizeOfBFH + sizeOfBIH);

            try
            {
                // Get the DIB
                isg.GetCurrentBuffer(ref len, ptrImg);

                // Create header for a file of type .bmp
                BITMAPFILEHEADER bfh = new BITMAPFILEHEADER();
                bfh.Type      = (UInt16)((((byte)'M') << 8) | ((byte)'B'));
                bfh.Size      = (uint)(len + sizeOfBFH + sizeOfBIH);
                bfh.Reserved1 = 0;
                bfh.Reserved2 = 0;
                bfh.OffBits   = (uint)(sizeOfBFH + sizeOfBIH);

                // Copy the BFH into unmanaged memory, so that we can copy
                // everything into a managed byte array all at once
                Marshal.StructureToPtr(bfh, ptrBlock, false);

                Marshal.StructureToPtr(bih, ptrBIH, false);

                // Pull it out of unmanaged memory into a managed byte[]
                byte[] img = new byte[len + sizeOfBFH + sizeOfBIH];
                Marshal.Copy(ptrBlock, img, 0, len + sizeOfBFH + sizeOfBIH);

                //System.IO.File.WriteAllBytes("cxp.bmp", img);

                System.IO.MemoryStream m = new System.IO.MemoryStream(img);
                this.m_Image      = Image.FromStream(m);
                this.DialogResult = DialogResult.OK;
            }
            finally
            {
                // Free the unmanaged memory
                if (ptrBlock != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(ptrBlock);
                }
                this.Close();
            }
        }
예제 #11
0
        // Grab a snapshot of the most recent image played.
        // Returns A pointer to the raw pixel data. Caller must release this memory with
        // Marshal.FreeCoTaskMem when it is no longer needed.
        public IntPtr SnapShot()
        {
            int    hr;
            IntPtr ip        = IntPtr.Zero;
            int    iBuffSize = 0;

            // Read the buffer size
            hr = m_sampGrabber.GetCurrentBuffer(ref iBuffSize, ip);
            DsError.ThrowExceptionForHR(hr);

            Debug.Assert(iBuffSize == m_ImageSize, "Unexpected buffer size");

            // Allocate the buffer and read it
            ip = Marshal.AllocCoTaskMem(iBuffSize);

            hr = m_sampGrabber.GetCurrentBuffer(ref iBuffSize, ip);
            DsError.ThrowExceptionForHR(hr);

            return(ip);
        }
예제 #12
0
        public byte[] GetRawBytes()
        {
            int sz = 0;

            i_grabber.GetCurrentBuffer(ref sz, IntPtr.Zero); // IntPtr.Zeroで呼び出してバッファサイズ取得
            if (sz == 0)
            {
                return(null);
            }

            // メモリ確保し画像データ取得
            var ptr = Marshal.AllocCoTaskMem(sz);

            i_grabber.GetCurrentBuffer(ref sz, ptr);

            // 画像データをbyte配列に入れなおす
            var data = new byte[sz];

            Marshal.Copy(ptr, data, 0, sz);
            Marshal.FreeCoTaskMem(ptr);
            return(data);
        }
예제 #13
0
        /// <summary>Screen grab the current image. Graph can be paused, playing, or stopped</summary>
        public Bitmap Snapshot()
        {
            // Grab a snapshot of the most recent image played.
            // Returns A pointer to the raw pixel data.
            // Caller must release this memory with Marshal.FreeCoTaskMem when it is no longer needed.
            IntPtr ip = IntPtr.Zero;

            try
            {
                // Read the buffer size
                int bufsize = 0;
                DsError.ThrowExceptionForHR(m_samp_grabber.GetCurrentBuffer(ref bufsize, IntPtr.Zero));

                // Allocate the buffer and read it
                ip = Marshal.AllocCoTaskMem(bufsize);
                DsError.ThrowExceptionForHR(m_samp_grabber.GetCurrentBuffer(ref bufsize, ip));

                // We know the Bits Per Pixel is 24 (3 bytes) because
                // we forced it to be with sampGrabber.SetMediaType()
                Size native_size = NativeSize;
                int  stride      = bufsize / native_size.Height;
                Debug.Assert((bufsize % native_size.Height) == 0);

                return(new Bitmap(
                           native_size.Width,
                           native_size.Height,
                           -stride,
                           PixelFormat.Format24bppRgb,
                           (IntPtr)(ip.ToInt32() + bufsize - stride)));
            }
            finally { if (ip != IntPtr.Zero)
                      {
                          Marshal.FreeCoTaskMem(ip);
                      }
            }
        }
예제 #14
0
        private static Bitmap GetBitmap(IGraphBuilder graph, ISampleGrabber sg, long grabPosition, out EventCode ec)
        {
            IntPtr pBuffer     = IntPtr.Zero;
            int    pBufferSize = 0;
            Bitmap b           = null;
            int    hr          = 0;

            try
            {
                IMediaSeeking ims = graph as IMediaSeeking;

                bool canDuration = false;
                bool canPos      = false;
                bool canSeek     = false;
                long pDuration   = 0;
                long pCurrent    = 0;

                if (ims != null)
                {
                    AMSeekingSeekingCapabilities caps;

                    hr = ims.GetCapabilities(out caps);
                    if ((caps & AMSeekingSeekingCapabilities.CanGetDuration) == AMSeekingSeekingCapabilities.CanGetDuration)
                    {
                        canDuration = true;
                    }
                    if ((caps & AMSeekingSeekingCapabilities.CanGetCurrentPos) == AMSeekingSeekingCapabilities.CanGetCurrentPos)
                    {
                        canPos = true;
                    }
                    if ((caps & AMSeekingSeekingCapabilities.CanSeekAbsolute) == AMSeekingSeekingCapabilities.CanSeekAbsolute)
                    {
                        canSeek = true;
                    }

                    if (canDuration)
                    {
                        hr = ims.GetDuration(out pDuration);
                    }

                    if (grabPosition > pDuration)
                    {
                        grabPosition = pDuration - 1;
                    }

                    if (canSeek)
                    {
                        hr = ims.SetPositions(new DsLong(grabPosition), AMSeekingSeekingFlags.AbsolutePositioning, 0, AMSeekingSeekingFlags.NoPositioning);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    if (canPos)
                    {
                        hr = ims.GetCurrentPosition(out pCurrent);
                    }
                }

                if (canPos)
                {
                    hr = ims.GetCurrentPosition(out pCurrent);
                }

                IMediaControl mControl = graph as IMediaControl;
                IMediaEvent   mEvent   = graph as IMediaEvent;

                //ec = EventCode.SystemBase;

                hr = mControl.Pause();
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Run();
                DsError.ThrowExceptionForHR(hr);

                hr = mEvent.WaitForCompletion(int.MaxValue, out ec);
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Pause();
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Stop();
                DsError.ThrowExceptionForHR(hr);

                if (ec != EventCode.Complete)
                {
                    return(null);
                }

                hr = sg.GetCurrentBuffer(ref pBufferSize, pBuffer);
                DsError.ThrowExceptionForHR(hr);

                pBuffer = Marshal.AllocCoTaskMem(pBufferSize);

                hr = sg.GetCurrentBuffer(ref pBufferSize, pBuffer);
                DsError.ThrowExceptionForHR(hr);

                if (pBuffer != IntPtr.Zero)
                {
                    AMMediaType sgMt        = new AMMediaType();
                    int         videoWidth  = 0;
                    int         videoHeight = 0;
                    int         stride      = 0;

                    try
                    {
                        hr = sg.GetConnectedMediaType(sgMt);
                        DsError.ThrowExceptionForHR(hr);

                        if (sgMt.formatPtr != IntPtr.Zero)
                        {
                            if (sgMt.formatType == FormatType.VideoInfo)
                            {
                                VideoInfoHeader vih = (VideoInfoHeader)Marshal.PtrToStructure(sgMt.formatPtr, typeof(VideoInfoHeader));
                                videoWidth  = vih.BmiHeader.Width;
                                videoHeight = vih.BmiHeader.Height;
                                stride      = videoWidth * (vih.BmiHeader.BitCount / 8);
                            }
                            else
                            {
                                throw new ApplicationException("Unsupported Sample");
                            }

                            b = new Bitmap(videoWidth, videoHeight, stride, System.Drawing.Imaging.PixelFormat.Format32bppRgb, pBuffer);
                            b.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        }
                    }
                    finally
                    {
                        DsUtils.FreeAMMediaType(sgMt);
                    }
                }

                return(b);
            }
            finally
            {
                if (pBuffer != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(pBuffer);
                }
            }
        }
예제 #15
0
        private static Bitmap GetBitmap(IGraphBuilder graph, ISampleGrabber sg, long grabPosition, out EventCode ec)
        {
            IntPtr pBuffer = IntPtr.Zero;
            int pBufferSize = 0;
            Bitmap b = null;
            int hr = 0;

            try
            {
                IMediaSeeking ims = graph as IMediaSeeking;

                bool canDuration = false;
                bool canPos = false;
                bool canSeek = false;
                long pDuration = 0;
                long pCurrent = 0;

                if (ims != null)
                {
                    AMSeekingSeekingCapabilities caps;

                    hr = ims.GetCapabilities(out caps);
                    if ((caps & AMSeekingSeekingCapabilities.CanGetDuration) == AMSeekingSeekingCapabilities.CanGetDuration)
                        canDuration = true;
                    if ((caps & AMSeekingSeekingCapabilities.CanGetCurrentPos) == AMSeekingSeekingCapabilities.CanGetCurrentPos)
                        canPos = true;
                    if ((caps & AMSeekingSeekingCapabilities.CanSeekAbsolute) == AMSeekingSeekingCapabilities.CanSeekAbsolute)
                        canSeek = true;

                    if (canDuration)
                        hr = ims.GetDuration(out pDuration);

                    if (grabPosition > pDuration)
                        grabPosition = pDuration - 1;

                    if (canSeek)
                    {
                        hr = ims.SetPositions(new DsLong(grabPosition), AMSeekingSeekingFlags.AbsolutePositioning, 0, AMSeekingSeekingFlags.NoPositioning);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    if (canPos)
                        hr = ims.GetCurrentPosition(out pCurrent);
                }

                if (canPos)
                    hr = ims.GetCurrentPosition(out pCurrent);

                IMediaControl mControl = graph as IMediaControl;
                IMediaEvent mEvent = graph as IMediaEvent;

                //ec = EventCode.SystemBase;

                hr = mControl.Pause();
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Run();
                DsError.ThrowExceptionForHR(hr);

                hr = mEvent.WaitForCompletion(int.MaxValue, out ec);
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Pause();
                DsError.ThrowExceptionForHR(hr);

                hr = mControl.Stop();
                DsError.ThrowExceptionForHR(hr);

                if (ec != EventCode.Complete)
                    return null;

                hr = sg.GetCurrentBuffer(ref pBufferSize, pBuffer);
                DsError.ThrowExceptionForHR(hr);

                pBuffer = Marshal.AllocCoTaskMem(pBufferSize);

                hr = sg.GetCurrentBuffer(ref pBufferSize, pBuffer);
                DsError.ThrowExceptionForHR(hr);

                if (pBuffer != IntPtr.Zero)
                {
                    AMMediaType sgMt = new AMMediaType();
                    int videoWidth = 0;
                    int videoHeight = 0;
                    int stride = 0;

                    try
                    {
                        hr = sg.GetConnectedMediaType(sgMt);
                        DsError.ThrowExceptionForHR(hr);

                        if (sgMt.formatPtr != IntPtr.Zero)
                        {
                            if (sgMt.formatType == FormatType.VideoInfo)
                            {
                                VideoInfoHeader vih = (VideoInfoHeader)Marshal.PtrToStructure(sgMt.formatPtr, typeof(VideoInfoHeader));
                                videoWidth = vih.BmiHeader.Width;
                                videoHeight = vih.BmiHeader.Height;
                                stride = videoWidth * (vih.BmiHeader.BitCount / 8);
                            }
                            else
                                throw new ApplicationException("Unsupported Sample");

                            b = new Bitmap(videoWidth, videoHeight, stride, System.Drawing.Imaging.PixelFormat.Format32bppRgb, pBuffer);
                            b.RotateFlip(RotateFlipType.RotateNoneFlipY);
                        }
                    }
                    finally
                    {
                        DsUtils.FreeAMMediaType(sgMt);
                    }
                }

                return b;
            }
            finally
            {
                if (pBuffer != IntPtr.Zero)
                    Marshal.FreeCoTaskMem(pBuffer);
            }
        }