示例#1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="WebpEncoderCore"/> class.
 /// </summary>
 /// <param name="options">The encoder options.</param>
 /// <param name="memoryAllocator">The memory manager.</param>
 public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAllocator)
 {
     this.memoryAllocator      = memoryAllocator;
     this.alphaCompression     = options.UseAlphaCompression;
     this.fileFormat           = options.FileFormat;
     this.quality              = options.Quality;
     this.method               = options.Method;
     this.entropyPasses        = options.EntropyPasses;
     this.spatialNoiseShaping  = options.SpatialNoiseShaping;
     this.filterStrength       = options.FilterStrength;
     this.transparentColorMode = options.TransparentColorMode;
     this.nearLossless         = options.NearLossless;
     this.nearLosslessQuality  = options.NearLosslessQuality;
 }
示例#2
0
        /// <summary>
        /// Stores the difference between the pixel and its prediction in "output".
        /// In case of a lossy encoding, updates the source image to avoid propagating
        /// the deviation further to pixels which depend on the current pixel for their
        /// predictions.
        /// </summary>
        private static void GetResidual(
            int width,
            int height,
            Span <uint> upperRowSpan,
            Span <uint> currentRowSpan,
            Span <byte> maxDiffs,
            int mode,
            int xStart,
            int xEnd,
            int y,
            int maxQuantization,
            WebpTransparentColorMode transparentColorMode,
            bool usedSubtractGreen,
            bool nearLossless,
            Span <uint> output,
            Span <short> scratch)
        {
            if (transparentColorMode == WebpTransparentColorMode.Preserve)
            {
                PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output, scratch);
            }
            else
            {
#pragma warning disable SA1503 // Braces should not be omitted
                fixed(uint *currentRow = currentRowSpan)
                fixed(uint *upperRow = upperRowSpan)
                {
                    for (int x = xStart; x < xEnd; x++)
                    {
                        uint predict = 0;
                        uint residual;
                        if (y == 0)
                        {
                            predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1];  // Left.
                        }
                        else if (x == 0)
                        {
                            predict = upperRow[x];  // Top.
                        }
                        else
                        {
                            switch (mode)
                            {
                            case 0:
                                predict = WebpConstants.ArgbBlack;
                                break;

                            case 1:
                                predict = currentRow[x - 1];
                                break;

                            case 2:
                                predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x);
                                break;

                            case 3:
                                predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x);
                                break;

                            case 4:
                                predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x);
                                break;

                            case 5:
                                predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x);
                                break;

                            case 6:
                                predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x);
                                break;

                            case 7:
                                predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x);
                                break;

                            case 8:
                                predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x);
                                break;

                            case 9:
                                predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x);
                                break;

                            case 10:
                                predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x);
                                break;

                            case 11:
                                predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x, scratch);
                                break;

                            case 12:
                                predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x);
                                break;

                            case 13:
                                predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x);
                                break;
                            }
                        }

                        if (nearLossless)
                        {
                            if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1)
                            {
                                residual = LosslessUtils.SubPixels(currentRow[x], predict);
                            }
                            else
                            {
                                residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen);

                                // Update the source image.
                                currentRow[x] = LosslessUtils.AddPixels(predict, residual);

                                // x is never 0 here so we do not need to update upperRow like below.
                            }
                        }
                        else
                        {
                            residual = LosslessUtils.SubPixels(currentRow[x], predict);
                        }

                        if ((currentRow[x] & MaskAlpha) == 0)
                        {
                            // If alpha is 0, cleanup RGB. We can choose the RGB values of the
                            // residual for best compression. The prediction of alpha itself can be
                            // non-zero and must be kept though. We choose RGB of the residual to be
                            // 0.
                            residual &= MaskAlpha;

                            // Update the source image.
                            currentRow[x] = predict & ~MaskAlpha;

                            // The prediction for the rightmost pixel in a row uses the leftmost
                            // pixel
                            // in that row as its top-right context pixel. Hence if we change the
                            // leftmost pixel of current_row, the corresponding change must be
                            // applied
                            // to upperRow as well where top-right context is being read from.
                            if (x == 0 && y != 0)
                            {
                                upperRow[width] = currentRow[0];
                            }
                        }

                        output[x - xStart] = residual;
                    }
                }
            }
        }
示例#3
0
        /// <summary>
        /// Finds the best predictor for each tile, and converts the image to residuals
        /// with respect to predictions. If nearLosslessQuality &lt; 100, applies
        /// near lossless processing, shaving off more bits of residuals for lower qualities.
        /// </summary>
        public static void ResidualImage(
            int width,
            int height,
            int bits,
            Span <uint> bgra,
            Span <uint> bgraScratch,
            Span <uint> image,
            int[][] histoArgb,
            int[][] bestHisto,
            bool nearLossless,
            int nearLosslessQuality,
            WebpTransparentColorMode transparentColorMode,
            bool usedSubtractGreen,
            bool lowEffort)
        {
            int          tilesPerRow     = LosslessUtils.SubSampleSize(width, bits);
            int          tilesPerCol     = LosslessUtils.SubSampleSize(height, bits);
            int          maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality);
            Span <short> scratch         = stackalloc short[8];

            // TODO: Can we optimize this?
            int[][] histo = new int[4][];
            for (int i = 0; i < 4; i++)
            {
                histo[i] = new int[256];
            }

            if (lowEffort)
            {
                for (int i = 0; i < tilesPerRow * tilesPerCol; i++)
                {
                    image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8);
                }
            }
            else
            {
                for (int tileY = 0; tileY < tilesPerCol; tileY++)
                {
                    for (int tileX = 0; tileX < tilesPerRow; tileX++)
                    {
                        int pred = GetBestPredictorForTile(
                            width,
                            height,
                            tileX,
                            tileY,
                            bits,
                            histo,
                            bgraScratch,
                            bgra,
                            histoArgb,
                            bestHisto,
                            maxQuantization,
                            transparentColorMode,
                            usedSubtractGreen,
                            nearLossless,
                            image,
                            scratch);

                        image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8));
                    }
                }
            }

            CopyImageWithPrediction(
                width,
                height,
                bits,
                image,
                bgraScratch,
                bgra,
                maxQuantization,
                transparentColorMode,
                usedSubtractGreen,
                nearLossless,
                lowEffort);
        }
示例#4
0
        /// <summary>
        /// Returns best predictor and updates the accumulated histogram.
        /// If maxQuantization > 1, assumes that near lossless processing will be
        /// applied, quantizing residuals to multiples of quantization levels up to
        /// maxQuantization (the actual quantization level depends on smoothness near
        /// the given pixel).
        /// </summary>
        /// <returns>Best predictor.</returns>
        private static int GetBestPredictorForTile(
            int width,
            int height,
            int tileX,
            int tileY,
            int bits,
            int[][] accumulated,
            Span <uint> argbScratch,
            Span <uint> argb,
            int[][] histoArgb,
            int[][] bestHisto,
            int maxQuantization,
            WebpTransparentColorMode transparentColorMode,
            bool usedSubtractGreen,
            bool nearLossless,
            Span <uint> modes,
            Span <short> scratch)
        {
            const int numPredModes = 14;
            int       startX       = tileX << bits;
            int       startY       = tileY << bits;
            int       tileSize     = 1 << bits;
            int       maxY         = GetMin(tileSize, height - startY);
            int       maxX         = GetMin(tileSize, width - startX);

            // Whether there exist columns just outside the tile.
            int haveLeft = startX > 0 ? 1 : 0;

            // Position and size of the strip covering the tile and adjacent columns if they exist.
            int contextStartX = startX - haveLeft;
            int contextWidth  = maxX + haveLeft + (maxX < width ? 1 : 0) - startX;
            int tilesPerRow   = LosslessUtils.SubSampleSize(width, bits);

            // Prediction modes of the left and above neighbor tiles.
            int leftMode  = (int)(tileX > 0 ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff);
            int aboveMode = (int)(tileY > 0 ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff);

            // The width of upper_row and current_row is one pixel larger than image width
            // to allow the top right pixel to point to the leftmost pixel of the next row
            // when at the right edge.
            Span <uint> upperRow   = argbScratch;
            Span <uint> currentRow = upperRow.Slice(width + 1);
            Span <byte> maxDiffs   = MemoryMarshal.Cast <uint, byte>(currentRow.Slice(width + 1));
            float       bestDiff   = MaxDiffCost;
            int         bestMode   = 0;

            uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
            for (int i = 0; i < 4; i++)
            {
                histoArgb[i].AsSpan().Clear();
                bestHisto[i].AsSpan().Clear();
            }

            for (int mode = 0; mode < numPredModes; mode++)
            {
                if (startY > 0)
                {
                    // Read the row above the tile which will become the first upper_row.
                    // Include a pixel to the left if it exists; include a pixel to the right
                    // in all cases (wrapping to the leftmost pixel of the next row if it does
                    // not exist).
                    Span <uint> src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1);
                    Span <uint> dst = currentRow.Slice(contextStartX);
                    src.CopyTo(dst);
                }

                for (int relativeY = 0; relativeY < maxY; relativeY++)
                {
                    int         y   = startY + relativeY;
                    Span <uint> tmp = upperRow;
                    upperRow   = currentRow;
                    currentRow = tmp;

                    // Read currentRow. Include a pixel to the left if it exists; include a
                    // pixel to the right in all cases except at the bottom right corner of
                    // the image (wrapping to the leftmost pixel of the next row if it does
                    // not exist in the currentRow).
                    int         offset = (y * width) + contextStartX;
                    Span <uint> src    = argb.Slice(offset, maxX + haveLeft + (y + 1 < height ? 1 : 0));
                    Span <uint> dst    = currentRow.Slice(contextStartX);
                    src.CopyTo(dst);

                    if (nearLossless)
                    {
                        if (maxQuantization > 1 && y >= 1 && y + 1 < height)
                        {
                            MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs.Slice(contextStartX), usedSubtractGreen);
                        }
                    }

                    GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, residuals, scratch);
                    for (int relativeX = 0; relativeX < maxX; ++relativeX)
                    {
                        UpdateHisto(histoArgb, residuals[relativeX]);
                    }
                }

                float curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb);

                // Favor keeping the areas locally similar.
                if (mode == leftMode)
                {
                    curDiff -= SpatialPredictorBias;
                }

                if (mode == aboveMode)
                {
                    curDiff -= SpatialPredictorBias;
                }

                if (curDiff < bestDiff)
                {
                    int[][] tmp = histoArgb;
                    histoArgb = bestHisto;
                    bestHisto = tmp;
                    bestDiff  = curDiff;
                    bestMode  = mode;
                }

                for (int i = 0; i < 4; i++)
                {
                    histoArgb[i].AsSpan().Clear();
                }
            }

            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 256; j++)
                {
                    accumulated[i][j] += bestHisto[i][j];
                }
            }

            return(bestMode);
        }