public void Write(FrameCompressor.Block block) { int sourceX = (block.Index % mSourceBlocksX) * mBlockSize; int sourceY = (block.Index / mSourceBlocksX) * mBlockSize; if (block.Index != mSourceIndex) { // Skip clear blocks if (mSkipsInARow == 0) { Flush(); } mSkipsInARow += block.Index - mSourceIndex; mSourceIndex = block.Index; } if (block.Score.HasFlag(FrameCompressor.Score.Solid)) { // Set solid color if different from previous color if (mSource[sourceX, sourceY] != mSolidColor) { Flush(); mSolidColor = mSource[sourceX, sourceY]; mSb.Append('s'); mSb.Append(mSolidColor & 0xFFFFFF); } // Cache solid color if (mSolidsInARow == 0) { Flush(); } mSolidsInARow++; mSourceIndex++; return; } if (block.Score.HasFlag(FrameCompressor.Score.Duplicate)) { // Set duplicate cursor position if different from previous location int duplicateCursor = mDuplicatedSourceToTarget[mDuplicates[block.Index]]; if (duplicateCursor < 0) { // This duplicate was not written to the target return; } if (duplicateCursor != mDuplicateCursor) { Flush(); mDuplicateCursor = duplicateCursor; mSb.Append('d'); mSb.Append(mDuplicateCursor); } // Cache duplicate block if (mDuplicatesInARow == 0) { Flush(); } mDuplicatesInARow++; mSourceIndex++; return; } // Create duplicate source to target map as we go if (mDuplicatedSourceToTarget.ContainsKey(mSourceIndex)) { mDuplicatedSourceToTarget[mSourceIndex] = mTargetIndex; } // Cache copy to target if (mCopiesInARow == 0) { Flush(); } mCopiesInARow++; mSourceIndex++; int targetX = (mTargetIndex % mTargetBlocksX) * mBlockSize; int targetY = (mTargetIndex / mTargetBlocksX) * mBlockSize; mTargetIndex++; // Copy source to target (source can be non block size multiple, but not target) mSource.Copy(sourceX, sourceY, Math.Min(mBlockSize, mSource.Width - sourceX), Math.Min(mBlockSize, mSource.Height - sourceY), mTarget, targetX, targetY); }
/// <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; }