{
    PixelGraphicPhotographicScaling реализует алгоритм масштабирования растровой
    графики «билинейная интерполяция», который является стандартным.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она может быть полезна,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit PixelGraphicPhotographicScaling;

{$MODE DELPHI}
{$ASMMODE INTEL}

interface

uses
    Lang,
    IntelMMX,
    PixelGraphicScaling;

{%region public }
type
    Algorithm = class(RefCountInterfacedObject, PixelGraphicScalingAlgorithm)
    private
        class function getAveragePixel(pixel0, pixel1: int): int; overload;
        class function getAveragePixel(pixel00, pixel01, pixel10, pixel11: int): int; overload;
        class function getAveragePixel1(pixel0, pixel1: int): int;
        class function getAveragePixel2(pixel0, pixel1: int): int;
        class function getAveragePixel11(pixel00, pixel01, pixel10, pixel11: int): int;
        class function getAveragePixel12(pixel00, pixel01, pixel10, pixel11: int): int;
        class function getAveragePixel21(pixel00, pixel01, pixel10, pixel11: int): int;
        class function getAveragePixel22(pixel00, pixel01, pixel10, pixel11: int): int;

    private
        inp: PIntArray;
        width: int;
        height: int;
        inScanline: int;
        square3x3: int_Array1d;
    public
        constructor create();
        procedure setSource(pixels: PIntArray; width, height, scanline: int);
        procedure scale2x(outp: PIntArray; outScanline: int; turned: boolean);
        procedure scale3x(outp: PIntArray; outScanline: int; turned: boolean);
        procedure scale4x(outp: PIntArray; outScanline: int; turned: boolean);
        function getSourceWidth(): int;
        function getSourceHeight(): int;
    end;
{%endregion}

implementation

{%region private }
var
    REGBLANK: long = $0000000000000000;
    CONST1D3: long = $0055005500550055;
    CONST2D3: long = $00ab00ab00ab00ab;
    CONST1D9: long = $001c001c001c001c;
    CONST2D9: long = $0039003900390039;
    CONST4D9: long = $0072007200720072;
{%endregion}

{%region Algorithm }
    class function Algorithm.getAveragePixel(pixel0, pixel1: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                paddw   mm0, mm1
                psrlw   mm0, $01
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel(pixel00, pixel01, pixel10, pixel11: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                movd    mm2, [esp+$08]
                movd    mm3, [esp+$04]
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                punpcklbw mm2, [REGBLANK]
                punpcklbw mm3, [REGBLANK]
                paddw   mm0, mm1
                paddw   mm0, mm2
                paddw   mm0, mm3
                psrlw   mm0, $02
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel1(pixel0, pixel1: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                pmullw  mm0, [CONST2D3]
                pmullw  mm1, [CONST1D3]
                paddw   mm0, mm1
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel2(pixel0, pixel1: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                pmullw  mm0, [CONST1D3]
                pmullw  mm1, [CONST2D3]
                paddw   mm0, mm1
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel11(pixel00, pixel01, pixel10, pixel11: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                movd    mm2, [esp+$08]
                movd    mm3, [esp+$04]
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                punpcklbw mm2, [REGBLANK]
                punpcklbw mm3, [REGBLANK]
                pmullw  mm0, [CONST4D9]
                pmullw  mm1, [CONST2D9]
                pmullw  mm2, [CONST2D9]
                pmullw  mm3, [CONST1D9]
                paddw   mm0, mm1
                paddw   mm0, mm2
                paddw   mm0, mm3
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel12(pixel00, pixel01, pixel10, pixel11: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                movd    mm2, [esp+$08]
                movd    mm3, [esp+$04]
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                punpcklbw mm2, [REGBLANK]
                punpcklbw mm3, [REGBLANK]
                pmullw  mm0, [CONST2D9]
                pmullw  mm1, [CONST4D9]
                pmullw  mm2, [CONST1D9]
                pmullw  mm3, [CONST2D9]
                paddw   mm0, mm1
                paddw   mm0, mm2
                paddw   mm0, mm3
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel21(pixel00, pixel01, pixel10, pixel11: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                movd    mm2, [esp+$08]
                movd    mm3, [esp+$04]
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                punpcklbw mm2, [REGBLANK]
                punpcklbw mm3, [REGBLANK]
                pmullw  mm0, [CONST2D9]
                pmullw  mm1, [CONST1D9]
                pmullw  mm2, [CONST4D9]
                pmullw  mm3, [CONST2D9]
                paddw   mm0, mm1
                paddw   mm0, mm2
                paddw   mm0, mm3
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    class function Algorithm.getAveragePixel22(pixel00, pixel01, pixel10, pixel11: int): int; assembler; nostackframe;
    asm
                movd    mm0, edx
                movd    mm1, ecx
                movd    mm2, [esp+$08]
                movd    mm3, [esp+$04]
                punpcklbw mm0, [REGBLANK]
                punpcklbw mm1, [REGBLANK]
                punpcklbw mm2, [REGBLANK]
                punpcklbw mm3, [REGBLANK]
                pmullw  mm0, [CONST1D9]
                pmullw  mm1, [CONST2D9]
                pmullw  mm2, [CONST2D9]
                pmullw  mm3, [CONST4D9]
                paddw   mm0, mm1
                paddw   mm0, mm2
                paddw   mm0, mm3
                psrlw   mm0, $08
                packuswb mm0, mm0
                movd    eax, mm0
    end;

    constructor Algorithm.create();
    begin
        inherited create();
        square3x3 := int_Array1d_create(3 * 3);
    end;

    procedure Algorithm.setSource(pixels: PIntArray; width, height, scanline: int);
    begin
        self.inp := pixels;
        self.width := width;
        self.height := height;
        self.inScanline := scanline;
    end;

    procedure Algorithm.scale2x(outp: PIntArray; outScanline: int; turned: boolean);
    var
        inp: PIntArray;
        inScanline: int;
        inWidth: int;
        inHeight: int;
        inCurrLine: int;
        inPixelIndex: int;
        outHeight: int;
        outCurrLine: int;
        outPixelIndex: int;
        i: int;
        j: int;
        pixel00: int;
        pixel01: int;
        pixel10: int;
        pixel11: int;
    begin
        inp := self.inp;
        inScanline := self.inScanline;
        inWidth := self.width;
        inHeight := self.height;
        inCurrLine := 0;
        outHeight := inHeight shl 1;
        outCurrLine := 0;
        if turned then begin
            outCurrLine := outScanline * (outHeight - 1);
            outScanline := -outScanline;
        end;
        for j := 0 to inHeight - 1 do begin
            for i := 0 to inWidth - 1 do begin
                inPixelIndex := inCurrLine + i;
                outPixelIndex := outCurrLine + (i shl 1);
                pixel00 := inp[inPixelIndex];
                if i < inWidth - 1 then begin
                    pixel01 := inp[inPixelIndex + 1];
                    if j < inHeight - 1 then begin
                        pixel10 := inp[inPixelIndex + inScanline];
                        pixel11 := inp[inPixelIndex + inScanline + 1];
                    end else begin
                        pixel10 := pixel00;
                        pixel11 := pixel01;
                    end;
                end else begin
                    pixel01 := pixel00;
                    if j < inHeight - 1 then begin
                        pixel10 := inp[inPixelIndex + inScanline];
                    end else begin
                        pixel10 := pixel00;
                    end;
                    pixel11 := pixel10;
                end;
                outp[outPixelIndex] := pixel00;
                outp[outPixelIndex + 1] := getAveragePixel(pixel00, pixel01);
                outp[outPixelIndex + outScanline] := getAveragePixel(pixel00, pixel10);
                outp[outPixelIndex + outScanline + 1] := getAveragePixel(pixel00, pixel01, pixel10, pixel11);
            end;
            inc(inCurrLine, inScanline);
            inc(outCurrLine, outScanline shl 1);
        end;
        returnToFPU();
    end;

    procedure Algorithm.scale3x(outp: PIntArray; outScanline: int; turned: boolean);
    var
        pixels: PIntArray;
        width: int;
        height: int;
        inScanline: int;
        pixCurrLine: int;
        pixPrevLine: int;
        pixNextLine: int;
        outCurrLine: int;
        outNext1Line: int;
        outNext2Line: int;
        outIndex: int;
        i: int;
        j: int;
        p: int_Array1d;
    begin
        pixels := self.inp;
        width := self.width;
        height := self.height;
        inScanline := self.inScanline;
        p := square3x3;
        pixCurrLine := 0;
        outCurrLine := 0;
        if turned then begin
            outCurrLine := outScanline * (height * 3 - 1);
            outScanline := -outScanline;
        end;
        for j := 0 to height - 1 do begin
            if j > 0 then begin
                pixPrevLine := pixCurrLine - inScanline;
            end else begin
                pixPrevLine := pixCurrLine;
            end;
            if j < height - 1 then begin
                pixNextLine := pixCurrLine + inScanline;
            end else begin
                pixNextLine := pixCurrLine;
            end;
            outNext1Line := outCurrLine + outScanline;
            outNext2Line := outNext1Line + outScanline;
            outIndex := 0;
            for i := 0 to width - 1 do begin
                p[1] := pixels[pixPrevLine + i];
                p[4] := pixels[pixCurrLine + i];
                p[7] := pixels[pixNextLine + i];
                if i > 0 then begin
                    p[0] := pixels[pixPrevLine + i - 1];
                    p[3] := pixels[pixCurrLine + i - 1];
                    p[6] := pixels[pixNextLine + i - 1];
                end else begin
                    p[0] := p[1];
                    p[3] := p[4];
                    p[6] := p[7];
                end;
                if i < width - 1 then begin
                    p[2] := pixels[pixPrevLine + i + 1];
                    p[5] := pixels[pixCurrLine + i + 1];
                    p[8] := pixels[pixNextLine + i + 1];
                end else begin
                    p[2] := p[1];
                    p[5] := p[4];
                    p[8] := p[7];
                end;
                {
                    p:
                    +---+---+---+
                    | 0 | 1 | 2 |
                    +---+---+---+
                    | 3 | 4 | 5 |
                    +---+---+---+
                    | 6 | 7 | 8 |
                    +---+---+---+
                }
                { Собственно сам алгоритм }
                outp[outCurrLine + outIndex] := getAveragePixel22(p[0], p[1], p[3], p[4]);
                outp[outCurrLine + outIndex + 1] := getAveragePixel2(p[1], p[4]);
                outp[outCurrLine + outIndex + 2] := getAveragePixel21(p[1], p[2], p[4], p[5]);
                outp[outNext1Line + outIndex] := getAveragePixel2(p[3], p[4]);
                outp[outNext1Line + outIndex + 1] := p[4];
                outp[outNext1Line + outIndex + 2] := getAveragePixel1(p[4], p[5]);
                outp[outNext2Line + outIndex] := getAveragePixel12(p[3], p[4], p[6], p[7]);
                outp[outNext2Line + outIndex + 1] := getAveragePixel1(p[4], p[7]);
                outp[outNext2Line + outIndex + 2] := getAveragePixel11(p[4], p[5], p[7], p[8]);
                { Конец алгоритма }
                inc(outIndex, 3);
            end;
            inc(pixCurrLine, inScanline);
            inc(outCurrLine, outScanline * 3);
        end;
    end;

    procedure Algorithm.scale4x(outp: PIntArray; outScanline: int; turned: boolean);
    var
        pixels: PIntArray;
        width: int;
        height: int;
        inScanline: int;
        pixCurrLine: int;
        pixPrevLine: int;
        pixNextLine: int;
        outCurrLine: int;
        outNext1Line: int;
        outNext2Line: int;
        outNext3Line: int;
        outIndex: int;
        i: int;
        j: int;
        p: int_Array1d;
    begin
        pixels := self.inp;
        width := self.width;
        height := self.height;
        inScanline := self.inScanline;
        p := square3x3;
        pixCurrLine := 0;
        outCurrLine := 0;
        if turned then begin
            outCurrLine := outScanline * (height * 4 - 1);
            outScanline := -outScanline;
        end;
        for j := 0 to height - 1 do begin
            if j > 0 then begin
                pixPrevLine := pixCurrLine - inScanline;
            end else begin
                pixPrevLine := pixCurrLine;
            end;
            if j < height - 1 then begin
                pixNextLine := pixCurrLine + inScanline;
            end else begin
                pixNextLine := pixCurrLine;
            end;
            outNext1Line := outCurrLine + outScanline;
            outNext2Line := outNext1Line + outScanline;
            outNext3Line := outNext2Line + outScanline;
            outIndex := 0;
            for i := 0 to width - 1 do begin
                p[1] := pixels[pixPrevLine + i];
                p[4] := pixels[pixCurrLine + i];
                p[7] := pixels[pixNextLine + i];
                if i > 0 then begin
                    p[0] := pixels[pixPrevLine + i - 1];
                    p[3] := pixels[pixCurrLine + i - 1];
                    p[6] := pixels[pixNextLine + i - 1];
                end else begin
                    p[0] := p[1];
                    p[3] := p[4];
                    p[6] := p[7];
                end;
                if i < width - 1 then begin
                    p[2] := pixels[pixPrevLine + i + 1];
                    p[5] := pixels[pixCurrLine + i + 1];
                    p[8] := pixels[pixNextLine + i + 1];
                end else begin
                    p[2] := p[1];
                    p[5] := p[4];
                    p[8] := p[7];
                end;
                {
                    p:
                    +---+---+---+
                    | 0 | 1 | 2 |
                    +---+---+---+
                    | 3 | 4 | 5 |
                    +---+---+---+
                    | 6 | 7 | 8 |
                    +---+---+---+
                }
                { Собственно сам алгоритм }
                p[0] := getAveragePixel(p[0], p[1], p[3], p[4]);
                p[2] := getAveragePixel(p[1], p[2], p[4], p[5]);
                p[6] := getAveragePixel(p[3], p[4], p[6], p[7]);
                p[8] := getAveragePixel(p[4], p[5], p[7], p[8]);
                p[1] := getAveragePixel(p[1], p[4]);
                p[3] := getAveragePixel(p[3], p[4]);
                p[5] := getAveragePixel(p[4], p[5]);
                p[7] := getAveragePixel(p[4], p[7]);
                outp[outCurrLine + outIndex] := getAveragePixel(p[0], p[1], p[3], p[4]);
                outp[outCurrLine + outIndex + 1] := getAveragePixel(p[1], p[4]);
                outp[outCurrLine + outIndex + 2] := getAveragePixel(p[1], p[2], p[4], p[5]);
                outp[outCurrLine + outIndex + 3] := getAveragePixel(p[2], p[5]);
                outp[outNext1Line + outIndex] := getAveragePixel(p[3], p[4]);
                outp[outNext1Line + outIndex + 1] := p[4];
                outp[outNext1Line + outIndex + 2] := getAveragePixel(p[4], p[5]);
                outp[outNext1Line + outIndex + 3] := p[5];
                outp[outNext2Line + outIndex] := getAveragePixel(p[3], p[4], p[6], p[7]);
                outp[outNext2Line + outIndex + 1] := getAveragePixel(p[4], p[7]);
                outp[outNext2Line + outIndex + 2] := getAveragePixel(p[4], p[5], p[7], p[8]);
                outp[outNext2Line + outIndex + 3] := getAveragePixel(p[5], p[8]);
                outp[outNext3Line + outIndex] := getAveragePixel(p[6], p[7]);
                outp[outNext3Line + outIndex + 1] := p[7];
                outp[outNext3Line + outIndex + 2] := getAveragePixel(p[7], p[8]);
                outp[outNext3Line + outIndex + 3] := p[8];
                { Конец алгоритма }
                inc(outIndex, 4);
            end;
            inc(pixCurrLine, inScanline);
            inc(outCurrLine, outScanline * 4);
        end;
    end;

    function Algorithm.getSourceWidth(): int;
    begin
        result := width;
    end;

    function Algorithm.getSourceHeight(): int;
    begin
        result := height;
    end;
{%endregion}

end.

