/// <summary> /// Adapted from https://github.com/google/brotli/blob/master/c/enc/encode.c (EncodeData). /// </summary> public (MetaBlock MetaBlock, BrotliEncodeInfo Next) Encode(BrotliEncodeInfo info) { var builder = info.NewBuilder(); do { position = lastProcessedPos; // TODO extend last command somehow??? int chunkLength = Math.Min(input.Length - position - lastInsertLen, InputBlockSize(lgBlock)); if (chunkLength == 0) { break; } hasher.StitchToPreviousBlock(chunkLength, position); lastProcessedPos = position + chunkLength; CreateBackwardReferences(builder, chunkLength); }while(lastProcessedPos + lastInsertLen < input.Length && ShouldContinueThisBlock(builder)); if (lastInsertLen > 0) { builder.AddInsert(Literal.FromBytes(info.Bytes.Slice(builder.OutputSize, lastInsertLen))); lastInsertLen = 0; } return(builder.Build(info)); }
private NextStep EmitRemainder(CompressedMetaBlockBuilder builder) { nextIpStart += blockSize; inputSize -= blockSize; blockSize = Math.Min(inputSize, MergeBlockSize); if (inputSize > 0 && totalBlockSize + blockSize <= (1 << 20) && ShouldMergeBlock(nextIpStart, blockSize)) { totalBlockSize += blockSize; return(NextStep.EmitCommands); } if (nextEmit < ipEnd) { int insert = ipEnd - nextEmit; if (insert >= 6210 && ShouldUseUncompressedMode(insert)) { outputTo = ipEnd; return(NextStep.OutputUncompressed); } else { builder.AddInsert(Literal.FromBytes(input, nextEmit, insert)); } } nextEmit = ipEnd; return(NextStep.OutputCompressed); }
public (MetaBlock, BrotliEncodeInfo) Encode(BrotliEncodeInfo info) { var bytes = CollectionHelper.SliceAtMost(info.Bytes, DataLength.MaxUncompressedBytes).ToArray(); return(info.NewBuilder() .AddInsert(Literal.FromBytes(bytes)) .Build(info)); }
/// <summary> /// Adapted from https://github.com/google/brotli/blob/master/c/enc/backward_references.c (BrotliCreateBackwardReferences). /// </summary> private void CreateBackwardReferences(CompressedMetaBlockBuilder builder, int chunkLength) { int maxBackwardLimit = fileParameters.WindowSize.Bytes; int insertLength = lastInsertLen; int posEnd = position + chunkLength; int storeEnd = chunkLength >= hasher.StoreLookahead ? posEnd - hasher.StoreLookahead + 1 : position; int applyRandomHeuristics = position + LiteralSpreeLengthForSparseSearch; while (position + hasher.HashTypeLength < posEnd) { int maxLength = posEnd - position; int maxDistance = Math.Min(position, maxBackwardLimit); int dictionaryStart = Math.Min(position, maxBackwardLimit); var result = hasher.FindLongestMatch(position, maxLength, maxDistance, dictionaryStart, builder.LastDistance, 0); if (result.FoundAnything) { int delayedBackwardReferencesInRow = 0; --maxLength; while (true) { maxDistance = Math.Min(position + 1, maxBackwardLimit); dictionaryStart = Math.Min(position + 1, maxBackwardLimit); var bestLenIn = features.HasFlag(Features.ExtensiveReferenceSearch) ? 0 : Math.Min(result.Copy.OutputLength - 1, maxLength); var result2 = hasher.FindLongestMatch(position + 1, maxLength, maxDistance, dictionaryStart, builder.LastDistance, bestLenIn); if (result2.Score >= result.Score + CostDiffLazy) { ++position; ++insertLength; result = result2; if (++delayedBackwardReferencesInRow < 4 && position + hasher.HashTypeLength < posEnd) { --maxLength; continue; } } break; } var copy = result.Copy; int len = copy.OutputLength; applyRandomHeuristics = position + (2 * len) + LiteralSpreeLengthForSparseSearch; copy.AddCommand(builder, Literal.FromBytes(input, position - insertLength, insertLength)); insertLength = 0; int rangeStart = position + 2; int rangeEnd = Math.Min(position + len, storeEnd); if (copy is Copy.BackReference backReference && backReference.Distance < len >> 2) { rangeStart = Math.Min(rangeEnd, Math.Max(rangeStart, position + len - (backReference.Distance << 2))); } hasher.StoreRange(rangeStart, rangeEnd); position += len; } else { ++insertLength; ++position; if (position > applyRandomHeuristics) { int skipStep; int nextStopOffset; if (position > applyRandomHeuristics + (4 * LiteralSpreeLengthForSparseSearch)) { skipStep = 4; nextStopOffset = 16; } else { skipStep = 2; nextStopOffset = 8; } int margin = Math.Max(hasher.StoreLookahead - 1, skipStep); int posJump = Math.Min(position + nextStopOffset, posEnd - margin); while (position < posJump) { hasher.Store(position); insertLength += skipStep; position += skipStep; } } } } insertLength += posEnd - position; lastInsertLen = insertLength; }
private NextStep Trawl(CompressedMetaBlockBuilder builder) { do { uint hash = nextHash; int bytesBetweenHashLookups = (skip++) >> 5; ip = nextIp; nextIp = ip + bytesBetweenHashLookups; if (nextIp > ipLimit) { return(NextStep.EmitRemainder); } nextHash = table.Hash(nextIp); candidate = ip - lastDistance; if (Match.Check(input, ip, candidate, MinMatchLen) && candidate < ip) { table[hash] = ip - baseIp; break; } candidate = baseIp + table[hash]; table[hash] = ip - baseIp; }while(!Match.Check(input, ip, candidate, MinMatchLen)); if (ip - candidate > SupportedWindowSize.Bytes) { return(NextStep.Trawl); } { int @base = ip; int matched = MinMatchLen + Match.DetermineLength(input, candidate + MinMatchLen, ip + MinMatchLen, ipEnd - ip - MinMatchLen); int distance = @base - candidate; int insert = @base - nextEmit; ip += matched; if (insert >= 6210 && ShouldUseUncompressedMode(insert)) { outputTo = @base; return(NextStep.OutputUncompressed); } if (distance == lastDistance) { builder.AddInsertCopy(Literal.FromBytes(input, nextEmit, insert), 2, DistanceInfo.ExplicitCodeZero); } else { builder.AddInsertCopy(Literal.FromBytes(input, nextEmit, insert), 2, distance); lastDistance = distance; } builder.AddCopy(matched - 2, distance); nextEmit = ip; if (ip >= ipLimit) { return(NextStep.EmitRemainder); } candidate = table.UpdateAndGetCandidate(ip, baseIp); } while (Match.Check(input, ip, candidate, MinMatchLen)) { int @base = ip; int matched = MinMatchLen + Match.DetermineLength(input, candidate + MinMatchLen, ip + MinMatchLen, ipEnd - ip - MinMatchLen); if (ip - candidate > SupportedWindowSize.Bytes) { break; } ip += matched; lastDistance = @base - candidate; builder.AddCopy(matched, lastDistance); nextEmit = ip; if (ip >= ipLimit) { return(NextStep.EmitRemainder); } candidate = table.UpdateAndGetCandidate(ip, baseIp); } nextHash = table.Hash(++ip); return(NextStep.EmitCommandsNextHash); }