Beispiel #1
0
        /// <summary>
        /// Retuns true if these two blocks the same
        /// </summary>
        public bool IsMatch(int x1, int y1, int width, int height, Bitmap32Bits bm2, int x2, int y2)
        {
            Debug.Assert(x1 >= 0 && x1 + width <= mWidth);
            Debug.Assert(y1 >= 0 && y1 + height <= mHeight);
            Debug.Assert(x2 >= 0 && x2 + width <= bm2.mWidth);
            Debug.Assert(y2 >= 0 && y2 + height <= bm2.mHeight);

            // Check for match
            int *bmPtr1 = mScan0 + y1 * mStrideInts + x1;
            int *bmPtr2 = bm2.mScan0 + y2 * bm2.mStrideInts + x2;

            while (--height >= 0)
            {
                int w = width;
                while (--w >= 0)
                {
                    if (*bmPtr1++ != *bmPtr2++)
                    {
                        return(false);
                    }
                }

                bmPtr1 += mStrideInts - width;
                bmPtr2 += bm2.mStrideInts - width;
            }
            return(true);
        }
Beispiel #2
0
        public BlockWriter(Bitmap32Bits source,
                           Bitmap32Bits target,
                           int blockSize,
                           Dictionary <int, int> duplicates)
        {
            mSource    = source;
            mTarget    = target;
            mBlockSize = blockSize;

            // The duplicates map from source index to source index,
            // but we want to map directly to target index.
            mDuplicates = duplicates;
            mDuplicatedSourceToTarget = new Dictionary <int, int>();
            foreach (var duplicate in mDuplicates)
            {
                mDuplicatedSourceToTarget[duplicate.Value] = -1;
            }

            // Number of blocks visible (even if partially visible)
            mSourceBlocksX = (source.Width + mBlockSize - 1) / mBlockSize;
            mTargetBlocksX = (target.Width + mBlockSize - 1) / mBlockSize;

            // Write bitmap header info - screen resolution and block size
            mSb.Append('X');
            mSb.Append(source.Width);
            mSb.Append('Y');
            mSb.Append(source.Height);
            mSb.Append('B');
            mSb.Append(mBlockSize);
        }
Beispiel #3
0
        private bool IsDuplicate(Bitmap32Bits bm,
                                 Dictionary <int, int> duplicateHashToIndex,
                                 int sourceIndex,
                                 out int duplicateIndex)
        {
            int x      = ItoX(sourceIndex);
            int y      = ItoY(sourceIndex);
            int width  = Math.Min(mBlockSize, bm.Width - x);
            int height = Math.Min(mBlockSize, bm.Height - y);

            int hash = bm.HashInt(x, y, width, height);

            if (duplicateHashToIndex.ContainsKey(hash))
            {
                // Possibly a match, but needs verification just in case there is a hash collison
                duplicateIndex = duplicateHashToIndex[hash];
                if (bm.IsMatch(x, y, width, height, bm, ItoX(duplicateIndex), ItoY(duplicateIndex)))
                {
                    return(true);
                }
                // Hash collision.  This is very rare.
                mHashCollisions++;
                duplicateIndex = 0;
                return(false);
            }
            // First time this block has been seen
            duplicateHashToIndex[hash] = sourceIndex;
            duplicateIndex             = 0;
            return(false);
        }
Beispiel #4
0
        /// <summary>
        /// Build a bitmap with any of the given score bits set
        /// </summary>
        Frame BuildBitmap(Bitmap32Bits frame,
                          List <Block> blocks,
                          Dictionary <int, int> duplicates,
                          Score score,
                          ImageFormat format)
        {
            // Count blocks to write to target
            int numTargetBlocks = 0;

            foreach (var block in blocks)
            {
                if ((block.Score & score & (Score.Jpg | Score.Png)) != Score.None)
                {
                    numTargetBlocks++;
                }
            }

            // Generate the bitmap
            int size        = (int)Math.Sqrt(numTargetBlocks) + 1;
            var bmTarget    = new Bitmap(size * mBlockSize, size * mBlockSize, PixelFormat.Format32bppArgb);
            var bmdTarget   = new Bitmap32Bits(bmTarget, ImageLockMode.WriteOnly);
            var blockWriter = new BlockWriter(frame, bmdTarget, mBlockSize, duplicates);

            foreach (var block in blocks)
            {
                if ((block.Score & score) != Score.None)
                {
                    blockWriter.Write(block);
                }
            }
            bmdTarget.Dispose();

            // Compress frame to stream
            var targetStream = new MemoryStream();

            bmTarget.Save(targetStream, format);
            bmTarget.Dispose();
            return(new Frame(blockWriter.GetDrawString(), targetStream, format));
        }
Beispiel #5
0
        /// <summary>
        /// Copy the screen, block for up to 2 seconds if nothing changes.
        /// </summary>
        void CopyScreen(RemoteEvents request, FrameCollector collector, CollectRequest collectRequest)
        {
            if (mOptions.FullThrottle)
            {
                // Need the lock to wait for previous throttle (if any) to end
                mLastScreenHash = 0; // Ask CopyScreen blocking to end early
                lock (mLockScreenCopy)
                    mLastScreenHash = 0;

                collector.CopyScreen();
            }
            else
            {
                // Block until screen changes or timeout
                // NOTE: Holding this lock (even without the hash function
                //       in the while loop) incurs a time penalty.  This
                //       code is the big bottleneck causing a slow frame
                //       rate.  And most of the time is spent in CopyScreen.
                lock (mLockScreenCopy)
                {
                    long hash = 0;
                    var  screenCopyTimeout = DateTime.Now;
                    while ((DateTime.Now - screenCopyTimeout).TotalMilliseconds < WAIT_FOR_SCREEN_CHANGE_TIMEOUT_MS)
                    {
                        collector.CopyScreen();
                        using (var bm = new Bitmap32Bits(collector.Screen, System.Drawing.Imaging.ImageLockMode.ReadOnly))
                            hash = bm.HashLong(0, 0, bm.Width, bm.Height);
                        if (hash != mLastScreenHash)
                        {
                            break;
                        }
                        Thread.Sleep(20);
                    }
                    mLastScreenHash = hash;
                }
            }
            collector.ScaleScreen(mOptions.Width, mOptions.Height);
        }
Beispiel #6
0
        /// <summary>
        /// Copy a block from this bitmap to the other bitmap.
        /// </summary>
        public void Copy(int x, int y, int width, int height, Bitmap32Bits toBm, int toX, int toY)
        {
            Debug.Assert(x >= 0 && x + width <= mWidth);
            Debug.Assert(y >= 0 && y + height <= mHeight);
            Debug.Assert(toX >= 0 && toX + width <= toBm.Width);
            Debug.Assert(toY >= 0 && toY + height <= toBm.Height);

            // Copy pixels
            int *fromPtr = mScan0 + y * mStrideInts + x;
            int *toPtr   = toBm.mScan0 + toY * toBm.mStrideInts + toX;

            while (--height >= 0)
            {
                int w = width;
                while (--w >= 0)
                {
                    *toPtr++ = *fromPtr++;
                }

                // Move to next line
                fromPtr += mStrideInts - width;
                toPtr   += toBm.mStrideInts - width;
            }
        }
Beispiel #7
0
        /// <summary>
        /// Score a frame, creating a list of blocks
        /// </summary>
        private void ScoreFrame(Bitmap32Bits frame,
                                bool disableDelta,
                                out List <Block> blocks,
                                out Dictionary <int, int> duplicates,
                                out int jpgCount,
                                out int pngCount)
        {
            blocks     = new List <Block>();
            duplicates = new Dictionary <int, int>();
            jpgCount   = 0;
            pngCount   = 0;

            var sourceIndex          = -1;
            var duplicateHashToIndex = new Dictionary <int, int>();

            for (int y = 0; y < frame.Height; y += mBlockSize)
            {
                for (int x = 0; x < frame.Width; x += mBlockSize)
                {
                    sourceIndex++;
                    Debug.Assert(ItoX(sourceIndex) == x);
                    Debug.Assert(ItoY(sourceIndex) == y);

                    // Skip clear blocks
                    int width  = Math.Min(mBlockSize, frame.Width - x);
                    int height = Math.Min(mBlockSize, frame.Height - y);
                    if (!disableDelta && frame.IsMatch(x, y, width, height, mBackground, x, y))
                    {
                        continue; // Nothing changed, skip it
                    }
                    frame.Copy(x, y, width, height, mBackground, x, y);

                    // Check for solid blocks
                    if (frame.IsSolid(x, y, width, height))
                    {
                        blocks.Add(new Block(sourceIndex, Score.Solid));
                        continue;
                    }
                    // Check for duplicates
                    int duplicateIndex;
                    if (IsDuplicate(frame, duplicateHashToIndex, sourceIndex, out duplicateIndex))
                    {
                        blocks.Add(new Block(sourceIndex, Score.Duplicate));
                        duplicates[sourceIndex] = duplicateIndex;
                        continue;
                    }
                    // Compression: SmartPNG, JPG, or PNG
                    bool usePng = mCompression == CompressionType.Png;
                    var  score  = Score.None;
                    if (mCompression == CompressionType.SmartPng && width >= 4 && height >= 4)
                    {
                        // SmartPng - Check to see if the block would compress well with PNG
                        var rleCount       = frame.RleCount(x, y, width, height);
                        var rleTotal       = (height - 1) * (width - 1);
                        var rleCompression = rleCount / (float)rleTotal;
                        score = (Score)((int)(rleCompression * 9.99) << (int)Score.ScoreShift) & Score.ScoreMask;
                        if (rleCompression > mPngCompressionThreshold)
                        {
                            if (!frame.LowFrequency(x, y, width, height))
                            {
                                usePng = true; // Good compression, not low frequency
                            }
                            else
                            {
                                score |= Score.LowFrequency;
                            }
                        }
                    }
                    blocks.Add(new Block(sourceIndex, score | (usePng ? Score.Png : Score.Jpg)));
                    if (usePng)
                    {
                        pngCount++;
                    }
                    else
                    {
                        jpgCount++;
                    }
                }
            }
            mDuplicates = duplicates.Count;
        }
Beispiel #8
0
        /// <summary>
        /// Analyze a bitmap, and keep a copy as the new background.  If the bitmap is not
        /// the same size as the previous one, a new comparison is made.  You must
        /// dispose the bitmap when done with it.  Returns 1 or 2 frames (PNG/JPG)
        /// depending on settings
        /// </summary>
        public Frame [] Compress(Bitmap frame)
        {
            // Generate full frame JPG or PNG (debugging output)
            if (mOutput == OutputType.FullFrameJpg || mOutput == OutputType.FullFramePng)
            {
                var f = GenerateFullFrame((Bitmap)frame, mOutput == OutputType.FullFrameJpg ? ImageFormat.Jpeg : ImageFormat.Png);
                return(new Frame[] { f });
            }

            // Force full frame analysis if requested
            bool disableDelta = mForceDisableDelta ||
                                mFullFrameAnalysis ||
                                Output == OutputType.HideJpg ||
                                Output == OutputType.HidePng;

            mForceDisableDelta = false;

            // Optionally create background if it doesn't exist, or the size changed
            if (mBackground == null || mBackground.Width != frame.Width || mBackground.Height != frame.Height)
            {
                // New background, do not compare with previous
                disableDelta = true;
                if (mBackground != null)
                {
                    mBackground.Dispose();
                }
                mBackground = new Bitmap32Bits(frame.Width, frame.Height);
            }

            // Number of blocks visible (even if partially visible)
            mBlocksX = (frame.Width + mBlockSize - 1) / mBlockSize;

            // Score bitmap
            mFrameIndex++;
            var                   frameBits = new Bitmap32Bits(frame, ImageLockMode.ReadOnly);
            List <Block>          blocks;
            Dictionary <int, int> duplicates;
            int                   jpgCount;
            int                   pngCount;

            ScoreFrame(frameBits, disableDelta, out blocks, out duplicates, out jpgCount, out pngCount);

            // Build the bitmaps (JPG and PNG)
            Frame[] frames;
            if (jpgCount == 0 || pngCount == 0)
            {
                // Build one frame (both PNG and JPG)
                frames = new Frame[]
                { BuildBitmap(frameBits, blocks,
                              duplicates, Score.Solid | Score.Duplicate | Score.Jpg | Score.Png,
                              pngCount == 0 ? ImageFormat.Jpeg : ImageFormat.Png) };
            }
            else
            {
                // Build two frames (PNG first, and JPG second)
                frames = new Frame[]
                { BuildBitmap(frameBits, blocks, duplicates, Score.Solid | Score.Duplicate | Score.Png, ImageFormat.Png),
                  BuildBitmap(frameBits, blocks, duplicates, Score.Duplicate | Score.Jpg, ImageFormat.Jpeg) };
            }
            frameBits.Dispose();

            // Optionally create compression maps and other debug output
            if (mOutput == OutputType.CompressionMap)
            {
                CopyDuplicateAttributesForDebug(blocks, duplicates);
                var map = GenerateCompressionMap(frame.Width, frame.Height, blocks);
                var f   = GenerateFullFrame(map, ImageFormat.Png);
                map.Dispose();
                return(new Frame[] { f });
            }
            else if (mOutput == OutputType.HideJpg || mOutput == OutputType.HidePng)
            {
                CopyDuplicateAttributesForDebug(blocks, duplicates);
                var map = GenerateHideMap(frame, blocks, mOutput == OutputType.HideJpg ? Score.Jpg : Score.Png);
                var f   = GenerateFullFrame(map, ImageFormat.Jpeg);
                map.Dispose();
                return(new Frame[] { f });
            }
            return(frames);
        }