All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.stanio.xbrz.Scaler Maven / Gradle / Ivy

Go to download

xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju, Java port by Stanio

There is a newer version: 1.8.2
Show newest version
package io.github.stanio.xbrz;

import static io.github.stanio.xbrz.Color.*;
import static io.github.stanio.xbrz.ColorGradient.gradientARGB;
import static io.github.stanio.xbrz.ColorGradient.gradientRGB;

interface Scaler {

    int scale();

    void blendLineShallow(int col, OutputMatrix out);

    void blendLineSteep(int col, OutputMatrix out);

    void blendLineSteepAndShallow(int col, OutputMatrix out);

    void blendLineDiagonal(int col, OutputMatrix out);

    void blendCorner(int col, OutputMatrix out);

    static Scaler forFactor(int factor, boolean withAlpha) {
        switch (factor) {
        case 2: return new Scaler2x(withAlpha);
        case 3: return new Scaler3x(withAlpha);
        case 4: return new Scaler4x(withAlpha);
        case 5: return new Scaler5x(withAlpha);
        case 6: return new Scaler6x(withAlpha);
        default:
            throw new IllegalArgumentException("Illegal scaling factor: " + factor);
        }
    }

}


abstract class AbstractScaler implements Scaler {

    protected final int scale;

    private ColorGradient colorGradient;

    protected AbstractScaler(int scale, boolean withAlpha) {
        this(scale, withAlpha ? gradientARGB() : gradientRGB());
    }

    protected AbstractScaler(int scale, ColorGradient colorGradient) {
        this.scale = scale;
        this.colorGradient = colorGradient;
    }

    @Override
    public final int scale() {
        return scale;
    }

    protected final int alphaGrad(int M, int N, int pixBack, int pixFront) {
        return colorGradient.alphaGrad(M, N, pixBack, pixFront);
    }

}


class Scaler2x extends AbstractScaler {

    public Scaler2x(boolean withAlpha) {
        super(2, withAlpha);
    }

    @Override
    public void blendLineShallow(int col, OutputMatrix out) {
        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
    }

    @Override
    public void blendLineSteep(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
    }

    @Override
    public void blendLineSteepAndShallow(int col, OutputMatrix out) {
        out.set(1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(0, 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(1, 1, ref -> alphaGrad(5, 6, ref, col)); // [!] fixes 7/8 used in xBR
    }

    @Override
    public void blendLineDiagonal(int col, OutputMatrix out) {
        out.set(1, 1, ref -> alphaGrad(1, 2, ref, col));
    }

    @Override
    public void blendCorner(int col, OutputMatrix out) {
        // model a round corner
        out.set(1, 1, ref -> alphaGrad(21, 100, ref, col)); // exact: 1 - pi/4 = 0.2146018366
    }

}


class Scaler3x extends AbstractScaler {

    public Scaler3x(boolean withAlpha) {
        super(3, withAlpha);
    }

    @Override
    public void blendLineShallow(int col, OutputMatrix out) {
        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));

        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 1, 2, col);
    }

    @Override
    public void blendLineSteep(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));

        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(2, scale - 1, col);
    }

    @Override
    public void blendLineSteepAndShallow(int col, OutputMatrix out) {
        out.set(2, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(0, 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(1, 2, ref -> alphaGrad(3, 4, ref, col));
        out.set(2, 2, col);
    }

    @Override
    public void blendLineDiagonal(int col, OutputMatrix out) {
        out.set(1, 2, ref -> alphaGrad(1, 8, ref, col)); // conflict with other rotations for this odd scale
        out.set(2, 1, ref -> alphaGrad(1, 8, ref, col));
        out.set(2, 2, ref -> alphaGrad(7, 8, ref, col)); //
    }

    @Override
    public void blendCorner(int col, OutputMatrix out) {
        // model a round corner
        out.set(2, 2, ref -> alphaGrad(45, 100, ref, col)); // exact: 0.4545939598
        //out.set(2, 1, ref -> alphaGrad(7, 256, ref, col)); // 0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale
        //out.set(1, 2, ref -> alphaGrad(7, 256, ref, col)); // 0.02826017254
    }

}


class Scaler4x extends AbstractScaler {

    public Scaler4x(boolean withAlpha) {
        super(4, withAlpha);
    }

    @Override
    public void blendLineShallow(int col, OutputMatrix out) {
        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));

        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 2, 3, ref -> alphaGrad(3, 4, ref, col));

        out.set(scale - 1, 2, col);
        out.set(scale - 1, 3, col);
    }

    @Override
    public void blendLineSteep(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));

        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(3, scale - 2, ref -> alphaGrad(3, 4, ref, col));

        out.set(2, scale - 1, col);
        out.set(3, scale - 1, col);
    }

    @Override
    public void blendLineSteepAndShallow(int col, OutputMatrix out) {
        out.set(3, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(1, 3, ref -> alphaGrad(3, 4, ref, col));
        out.set(3, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(0, 3, ref -> alphaGrad(1, 4, ref, col));

        out.set(2, 2, ref -> alphaGrad(1, 3, ref, col)); //[!] fixes 1/4 used in xBR

        out.set(3, 3, col);
        out.set(3, 2, col);
        out.set(2, 3, col);
    }

    @Override
    public void blendLineDiagonal(int col, OutputMatrix out) {
        out.set(scale - 1, scale / 2    , ref -> alphaGrad(1, 2, ref, col));
        out.set(scale - 2, scale / 2 + 1, ref -> alphaGrad(1, 2, ref, col));
        out.set(scale - 1, scale - 1, col);
    }

    @Override
    public void blendCorner(int col, OutputMatrix out) {
        // model a round corner
        out.set(3, 3, ref -> alphaGrad(68, 100, ref, col)); // exact: 0.6848532563
        out.set(3, 2, ref -> alphaGrad( 9, 100, ref, col)); // 0.08677704501
        out.set(2, 3, ref -> alphaGrad( 9, 100, ref, col)); // 0.08677704501
    }

}


class Scaler5x extends AbstractScaler {

    public Scaler5x(boolean withAlpha) {
        super(5, withAlpha);
    }

    @Override
    public void blendLineShallow(int col, OutputMatrix out) {
        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 3, 4, ref -> alphaGrad(1, 4, ref, col));

        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 2, 3, ref -> alphaGrad(3, 4, ref, col));

        out.set(scale - 1, 2, col);
        out.set(scale - 1, 3, col);
        out.set(scale - 1, 4, col);
        out.set(scale - 2, 4, col);
    }

    @Override
    public void blendLineSteep(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(4, scale - 3, ref -> alphaGrad(1, 4, ref, col));

        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(3, scale - 2, ref -> alphaGrad(3, 4, ref, col));

        out.set(2, scale - 1, col);
        out.set(3, scale - 1, col);
        out.set(4, scale - 1, col);
        out.set(4, scale - 2, col);
    }

    @Override
    public void blendLineSteepAndShallow(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));

        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));

        out.set(3, 3, ref -> alphaGrad(2, 3, ref, col));

        out.set(2, scale - 1, col);
        out.set(3, scale - 1, col);
        out.set(4, scale - 1, col);

        out.set(scale - 1, 2, col);
        out.set(scale - 1, 3, col);
    }

    @Override
    public void blendLineDiagonal(int col, OutputMatrix out) {
        out.set(scale - 1, scale / 2    , ref -> alphaGrad(1, 8, ref, col)); //conflict with other rotations for this odd scale
        out.set(scale - 2, scale / 2 + 1, ref -> alphaGrad(1, 8, ref, col));
        out.set(scale - 3, scale / 2 + 2, ref -> alphaGrad(1, 8, ref, col)); //

        out.set(4, 3, ref -> alphaGrad(7, 8, ref, col));
        out.set(3, 4, ref -> alphaGrad(7, 8, ref, col));

        out.set(4, 4, col);
    }

    @Override
    public void blendCorner(int col, OutputMatrix out) {
        // model a round corner
        out.set(4, 4, ref -> alphaGrad(86, 100, ref, col)); // exact: 0.8631434088
        out.set(4, 3, ref -> alphaGrad(23, 100, ref, col)); // 0.2306749731
        out.set(3, 4, ref -> alphaGrad(23, 100, ref, col)); // 0.2306749731
        //out.set(4, 2, ref -> alphaGrad(1, 64, ref, col)); // 0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale
        //out.set(2, 4, ref -> alphaGrad(1, 64, ref, col)); // 0.01676812367
    }

}


class Scaler6x extends AbstractScaler {

    public Scaler6x(boolean withAlpha) {
        super(6, withAlpha);
    }

    @Override
    public void blendLineShallow(int col, OutputMatrix out) {
        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 3, 4, ref -> alphaGrad(1, 4, ref, col));

        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 2, 3, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 3, 5, ref -> alphaGrad(3, 4, ref, col));

        out.set(scale - 1, 2, col);
        out.set(scale - 1, 3, col);
        out.set(scale - 1, 4, col);
        out.set(scale - 1, 5, col);

        out.set(scale - 2, 4, col);
        out.set(scale - 2, 5, col);
    }

    @Override
    public void blendLineSteep(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(4, scale - 3, ref -> alphaGrad(1, 4, ref, col));

        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(3, scale - 2, ref -> alphaGrad(3, 4, ref, col));
        out.set(5, scale - 3, ref -> alphaGrad(3, 4, ref, col));

        out.set(2, scale - 1, col);
        out.set(3, scale - 1, col);
        out.set(4, scale - 1, col);
        out.set(5, scale - 1, col);

        out.set(4, scale - 2, col);
        out.set(5, scale - 2, col);
    }

    @Override
    public void blendLineSteepAndShallow(int col, OutputMatrix out) {
        out.set(0, scale - 1, ref -> alphaGrad(1, 4, ref, col));
        out.set(2, scale - 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(1, scale - 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(3, scale - 2, ref -> alphaGrad(3, 4, ref, col));

        out.set(scale - 1, 0, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 2, 2, ref -> alphaGrad(1, 4, ref, col));
        out.set(scale - 1, 1, ref -> alphaGrad(3, 4, ref, col));
        out.set(scale - 2, 3, ref -> alphaGrad(3, 4, ref, col));

        out.set(2, scale - 1, col);
        out.set(3, scale - 1, col);
        out.set(4, scale - 1, col);
        out.set(5, scale - 1, col);

        out.set(4, scale - 2, col);
        out.set(5, scale - 2, col);

        out.set(scale - 1, 2, col);
        out.set(scale - 1, 3, col);
    }

    @Override
    public void blendLineDiagonal(int col, OutputMatrix out) {
        out.set(scale - 1, scale / 2    , ref -> alphaGrad(1, 2, ref, col));
        out.set(scale - 2, scale / 2 + 1, ref -> alphaGrad(1, 2, ref, col));
        out.set(scale - 3, scale / 2 + 2, ref -> alphaGrad(1, 2, ref, col));

        out.set(scale - 2, scale - 1, col);
        out.set(scale - 1, scale - 1, col);
        out.set(scale - 1, scale - 2, col);
    }

    @Override
    public void blendCorner(int col, OutputMatrix out) {
        // model a round corner
        out.set(5, 5, ref -> alphaGrad(97, 100, ref, col)); // exact: 0.9711013910
        out.set(4, 5, ref -> alphaGrad(42, 100, ref, col)); // 0.4236372243
        out.set(5, 4, ref -> alphaGrad(42, 100, ref, col)); // 0.4236372243
        out.set(5, 3, ref -> alphaGrad( 6, 100, ref, col)); // 0.05652034508
        out.set(3, 5, ref -> alphaGrad( 6, 100, ref, col)); // 0.05652034508
    }

}


interface ColorGradient {

    int alphaGrad(int M, int N, int pixBack, int pixFront);

    static ColorGradient gradientRGB() {
        return new ColorGradientRGB();
    }

    static ColorGradient gradientARGB() {
        return new ColorGradientARGB();
    }

}


class ColorGradientRGB implements ColorGradient {

    private static int calcColor(int M, int N, int colFront, int colBack) {
        return (colFront * M + colBack * (N - M)) / N;
    }

    @Override
    // blend front color with opacity M / N over opaque background: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
    public int alphaGrad(int M, int N, int pixBack, int pixFront) {
        //assert (0 < M && M < N && N <= 1000);

        return makePixel(calcColor(M, N, getRed(pixFront), getRed(pixBack)),
                         calcColor(M, N, getGreen(pixFront), getGreen(pixBack)),
                         calcColor(M, N, getBlue(pixFront), getBlue(pixBack)));
    }

}

class ColorGradientARGB implements ColorGradient {

    private static int calcColor(int weightFront, int weightBack, int weightSum, int colFront, int colBack) {
        return (colFront * weightFront + colBack * weightBack) / weightSum;
    }

    @Override
    // find intermediate color between two colors with alpha channels (=> NO alpha blending!!!)
    public int alphaGrad(int M, int N, int pixBack, int pixFront) {
        //assert (0 < M && M < N && N <= 1000);

        final int weightFront = getAlpha(pixFront) * M;
        final int weightBack = getAlpha(pixBack) * (N - M);
        final int weightSum = weightFront + weightBack;
        if (weightSum == 0)
            return 0;

        return makePixel(weightSum / N,
                         calcColor(weightFront, weightBack, weightSum, getRed(pixFront), getRed(pixBack)),
                         calcColor(weightFront, weightBack, weightSum, getGreen(pixFront), getGreen(pixBack)),
                         calcColor(weightFront, weightBack, weightSum, getBlue(pixFront), getBlue(pixBack)));
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy