{
    Images содержит классы и методы для работы с растровыми изображениями.
    Так же реализует чтение изображений в формате PNG (.png).

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

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

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

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

unit Images;

{$MODE DELPHI}
{$ASMMODE INTEL}

interface

uses
    Windows,
    Graphics,
    Lang,
    IOStreams,
    FileIO,
    Zlib;

{%region public }
const
    GRAPHIC_LISTENER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C150C}';

type
    GraphicListener = interface;
    Image = class;
    ImagePNG = class;

    GraphicListener = interface(_Interface) [GRAPHIC_LISTENER_GUID]
        procedure putPixel(x, y, argb: int);
    end;

    Image = class(_Object)
    public
        constructor create();
        function isEmpty(): boolean; virtual; abstract;
        function getWidth(): int; virtual; abstract;
        function getHeight(): int; virtual; abstract;
        procedure getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int); virtual; abstract;
        procedure loadFromStream(stream: Input); virtual; abstract;
        procedure loadFromFile(const fileName: AnsiString); overload; virtual;
        procedure loadFromFile(const fileName: UnicodeString); overload; virtual;
        procedure draw(listener: GraphicListener; originX, originY: int); overload; virtual;
        procedure draw(listener: GraphicListener; originX, originY, left, top, width, height: int); overload; virtual;
    end;

    ImagePNG = class(Image)
    strict private
        useGrayscale: boolean;
        usePalette: boolean;
        useAlpha: boolean;
        width: int;
        height: int;
        components: int;
        bitDepth: int;
        data: byte_Array1d;
        gamma: byte_Array1d;
        palette: int_Array1d;
        function readPixel(offset: int): int;
        function getLineSize(width, pixelType: int): int;
        function getFilterOffset(pixelType: int): int;
        function decodeNormal(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
        function decodeAdam7(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
        procedure unwrap1bitPP(source, dest: Pointer; length: int); stdcall;
        procedure unwrap2bitPP(source, dest: Pointer; length: int); stdcall;
        procedure unwrap4bitPP(source, dest: Pointer; length: int); stdcall;
        procedure unwrap1bitPC(source, dest: Pointer; length: int); stdcall;
        procedure unwrap2bitPC(source, dest: Pointer; length: int); stdcall;
        procedure unwrap4bitPC(source, dest: Pointer; length: int); stdcall;
        procedure unwrap8bitPC(source, dest: Pointer; length: int); stdcall;
        procedure unwrap16bitPC(source, dest: Pointer; length: int); stdcall;
        procedure init();
    public
        constructor create();
        function isEmpty(): boolean; override;
        function getWidth(): int; override;
        function getHeight(): int; override;
        procedure getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int); override;
        procedure loadFromStream(istream: Input); override;
    end;
{%endregion}

{%region routine }
    function computePixel(sourcePixel, drawPixel: int; sourceWithAlpha, drawWithAlpha: boolean): int;
    function createPixelImage(image: TBitmap; width, height: int): PIntArray;
{%endregion}

implementation

{$T-}

{%region private }
var
    CONST_0000: long = $0000000000000000;
    CONST_00FF: long = $00ff00ff00ff00ff;
    ALPHA_MASK: long = $0000ffffffffffff;
{%endregion}

{%region routine }
    function paethPredictor(a, b, c: int): int;
    var
        p: int;
        pa: int;
        pb: int;
        pc: int;
    begin
        p := a + b - c;
        pa := abs(p - a);
        pb := abs(p - b);
        pc := abs(p - c);
        if (pa <= pb) and (pa <= pc) then begin
            result := a;
            exit;
        end;
        if pb <= pc then begin
            result := b;
            exit;
        end;
        result := c;
    end;

    function getXStart(data: int): int; inline;
    begin
        result := (data shr 12) and $0f;
    end;

    function getYStart(data: int): int; inline;
    begin
        result := (data shr 8) and $0f;
    end;

    function getXInc(data: int): int; inline;
    begin
        result := (data shr 4) and $0f;
    end;

    function getYInc(data: int): int; inline;
    begin
        result := data and $0f;
    end;

    function power(base, exponent: real): real;
    begin
        if base <> 0.0 then begin
            result := pow2(exponent * log2(base));
            exit;
        end;
        result := 0.0;
    end;

    function computePixel(sourcePixel, drawPixel: int; sourceWithAlpha, drawWithAlpha: boolean): int; assembler; nostackframe;
    asm
                {
                Вход:   eax – sourcePixel
                        edx – drawPixel
                        cl  – sourceWithAlpha
                        byte[esp+$04] – drawWithAlpha
                Выход:  eax
                }
                mov     ch,  [esp+$04]
                test    cl,  cl
                jnz     @0
                or      eax, $ff000000
        @0:     test    ch,  ch
                jnz     @1
                or      edx, $ff000000
        @1:     rol     edx, $08
                lea     esp, [esp-$08]
                mov     byte [esp+$00], dl
                mov     byte [esp+$02], dl
                mov     byte [esp+$04], dl
                mov     byte [esp+$06], dl
                ror     edx, $08
                movq    mm0, [CONST_00FF]
                movd    mm1, edx
                punpcklbw mm1, [CONST_0000]
                and     dword[esp+$00], $00ff00ff
                and     dword[esp+$04], $00ff00ff
                pmullw  mm1, [esp+$00]
                paddw   mm0, mm1
                movd    mm1, eax
                punpcklbw mm1, [CONST_0000]
                xor     dword[esp+$00], $00ff00ff
                xor     dword[esp+$04], $00ff00ff
                pmullw  mm1, [esp+$00]
                paddw   mm0, mm1
                psrlw   mm0, $08
                pand    mm0, [ALPHA_MASK]
                test    cl,  cl
                jz      @2
                shr     eax, $18
                shr     edx, $18
                lea     ecx, [edx+$00]
                mov     dword[esp+$00], $000000ff
                sub     dword[esp+$00], eax
                imul    ecx, [esp+$00]
                xchg    eax, ecx
                xor     edx, edx
                mov     dword[esp+$00], $000000ff
                div     dword[esp+$00]
                lea     eax, [eax+ecx]
                shl     eax, $18
                packuswb mm0, mm0
                movd    edx, mm0
                or      eax, edx
                jmp     @3
        @2:     packuswb mm0, mm0
                movd    eax, mm0
        @3:     lea     esp, [esp+$08]
                emms
    end;

    function createPixelImage(image: TBitmap; width, height: int): PIntArray;
    var
        dc: Windows.HDC;
        info: Windows.BITMAPINFO;
    begin
        result := nil;
        image.handle := 0;
        initialize(info);
        fillChar(info, sizeof(Windows.BITMAPINFO), 0);
        with info.bmiHeader do begin
            biSize := sizeof(Windows.BITMAPINFOHEADER);
            biWidth := width;
            biHeight := height;
            biPlanes := 1;
            biBitCount := 32;
            biCompression := Windows.BI_RGB;
            biSizeImage := 0;
            biXPelsPerMeter := 0;
            biYPelsPerMeter := 0;
            biClrUsed := 0;
            biClrImportant := 0;
        end;
        dc := Windows.createDC('DISPLAY', nil, nil, nil);
        try
            image.handle := Windows.createDIBSection(dc, info, Windows.DIB_RGB_COLORS, Pointer(result), 0, 0);
        finally
            Windows.deleteDC(dc);
        end;
    end;
{%endregion}

{%region Image }
    constructor Image.create();
    begin
        inherited create();
    end;

    procedure Image.loadFromFile(const fileName: AnsiString);
    begin
        loadFromFile(toUTF16String(fileName));
    end;

    procedure Image.loadFromFile(const fileName: UnicodeString);
    var
        stream: FileInputStream;
    begin
        stream := FileInputStream.create(fileName);
        if stream.isInvalidHandle() then begin
            stream.free();
            exit;
        end;
        loadFromStream(stream);
    end;

    procedure Image.draw(listener: GraphicListener; originX, originY: int);
    begin
        draw(listener, originX, originY, 0, 0, getWidth(), getHeight());
    end;

    procedure Image.draw(listener: GraphicListener; originX, originY, left, top, width, height: int);
    var
        lim: int;
        len: int;
        idx: int;
        x: int;
        y: int;
        pixels: int_Array1d;
    begin
        lim := left + width;
        len := getWidth();
        if (lim > len) or (lim < left) or (left < 0) or (left > len) then begin
            raise IllegalArgumentException.create('Image.draw: заданная область выходит за пределы изображения.');
        end;
        lim := top + height;
        len := getHeight();
        if (lim > len) or (lim < top) or (top < 0) or (top > len) then begin
            raise IllegalArgumentException.create('Image.draw: заданная область выходит за пределы изображения.');
        end;
        pixels := int_Array1d_create(width * height);
        getPixels(pixels, 0, width, left, top, width, height);
        idx := 0;
        for y := 0 to height - 1 do begin
            for x := 0 to width - 1 do begin
                listener.putPixel(originX + x, originY, pixels[idx]);
                inc(idx);
            end;
            inc(originY);
        end;
    end;
{%endregion}

{%region ImagePNG }
    constructor ImagePNG.create();
    begin
        inherited create();
        init();
    end;

    function ImagePNG.readPixel(offset: int): int; assembler;
    var
        ourSelf: ImagePNG;
    asm
                { eax = self, edx = offset }
                mov     dword[ourSelf], eax
                test    byte [eax+offset usePalette], true
                jz      @0

                { тип пикселов – индексный цвет }
                mov     ecx, [eax+offset data]
                movzx   ecx, byte[ecx+edx]
                mov     edx, [eax+offset palette]
                mov     eax, [edx+ecx*4]
                jmp     @gamma
        @0:     test    byte [eax+offset useAlpha], true
                jz      @2
                test    byte [eax+offset useGrayscale], true
                jz      @1

                { тип пикселов – YA }
                mov     eax, [eax+offset data]
                movzx   eax, word[eax+edx] { $0000AAYY }
                mov     dl,  ah
                shl     edx, $18 { $AA000000 }
                mov     ah,  al  { $0000YYYY }
                shl     eax, $08 { $00YYYY00 }
                mov     al,  ah  { $00YYYYYY }
                or      eax, edx { $AAYYYYYY }
                jmp     @gamma

        @1:     { тип пикселов – RGBA }
                mov     eax, [eax+offset data]
                mov     eax, [eax+edx] { $AABBGGRR }
                bswap   eax  { $RRGGBBAA }
                ror     eax, $08 { $AARRGGBB }
                jmp     @gamma
        @2:     test    byte [eax+offset useGrayscale], true
                jz      @3

                { тип пикселов – Y }
                mov     eax, [eax+offset data]
                movzx   eax, byte[eax+edx] { $000000YY }
                mov     ah,  al  { $0000YYYY }
                shl     eax, $08 { $00YYYY00 }
                mov     al,  ah  { $00YYYYYY }
                or      eax, $ff000000 { $ffYYYYYY }
                jmp     @gamma

        @3:     { тип пикселов – RGB }
                mov     eax, [eax+offset data]
                mov     cx,  [eax+edx+$01] { $????BBGG }
                shl     ecx, $10 { $BBGG0000 }
                mov     ch,  [eax+edx] { $BBGGRR00 }
                mov     cl,  $ff { $BBGGRRff }
                bswap   ecx
                lea     eax, [ecx+$00] { $ffRRGGBB }

        @gamma: { применение гамма-коррекции… }
                mov     edx, [ourSelf]
                mov     edx, [edx+offset gamma]
                movzx   ecx, al
                mov     al,  [edx+ecx]
                mov     cl,  ah
                mov     ah,  [edx+ecx]
                ror     eax, $10
                mov     cl,  al
                mov     al,  [edx+ecx]
                rol     eax, $10
    end;

    function ImagePNG.getLineSize(width, pixelType: int): int;
    begin
        if (pixelType = 0) or (pixelType = 3) then begin
            result := (width * bitDepth + 7) div 8;
        end else begin
            result := (width * bitDepth * components) div 8;
        end;
    end;

    function ImagePNG.getFilterOffset(pixelType: int): int;
    begin
        if (pixelType = 0) or (pixelType = 3) then begin
            if bitDepth <> 16 then begin
                result := 1;
            end else begin
                result := 2;
            end;
        end else begin
            result := (bitDepth * components) div 8;
        end;
    end;

    function ImagePNG.decodeNormal(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
    type
        UnwrapMethod = procedure (source, dest: Pointer; length: int) of object stdcall;
    var
        f: int;
        i: int;
        j: int;
        k: int;
        ofs: int;
        filter: int;
        total: int;
        lineSize: int;
        unwrap: UnwrapMethod;
    begin
        result := startIndex;
        lineSize := getLineSize(imageWidth, pixelType);
        f := getFilterOffset(pixelType);
        k := lineSize + 1;
        ofs := 0;
        total := imageWidth * components;
        unwrap := nil;
        if usePalette then begin
            case bitDepth of
             1: unwrap := unwrap1bitPP;
             2: unwrap := unwrap2bitPP;
             4: unwrap := unwrap4bitPP;
             8: unwrap := unwrap8bitPC;
            end;
        end else begin
            case bitDepth of
             1: unwrap := unwrap1bitPC;
             2: unwrap := unwrap2bitPC;
             4: unwrap := unwrap4bitPC;
             8: unwrap := unwrap8bitPC;
            16: unwrap := unwrap16bitPC;
            end;
        end;
        for i := 0 to imageHeight - 1 do begin
            filter := int(stream[result]) and $ff;
            inc(result);
            case filter of
             1: begin
                for j := result + f to result + lineSize - 1 do begin
                    stream[j] := byte(stream[j] + stream[j - f]);
                end;
            end;
             2: begin
                if i > 0 then begin
                    for j := result to result + lineSize - 1 do begin
                        stream[j] := byte(stream[j] + stream[j - k]);
                    end;
                end;
            end;
             3: begin
                if i > 0 then begin
                    for j := result to result + f - 1 do begin
                        stream[j] := byte(stream[j] + ((int(stream[j - k]) and $ff) shr 1));
                    end;
                    for j := result + f to result + lineSize - 1 do begin
                        stream[j] := byte(stream[j] + (((int(stream[j - f]) and $ff) + (int(stream[j - k]) and $ff)) shr 1));
                    end;
                end else begin
                    for j := result + f to result + lineSize - 1 do begin
                        stream[j] := byte(stream[j] + ((int(stream[j - f]) and $ff) shr 1));
                    end;
                end;
            end;
             4: begin
                if i > 0 then begin
                    for j := result to result + f - 1 do begin
                        stream[j] := byte(stream[j] + paethPredictor(0, int(stream[j - k]) and $ff, 0));
                    end;
                    for j := result + f to result + lineSize - 1 do begin
                        stream[j] := byte(stream[j] + paethPredictor(int(stream[j - f]) and $ff, int(stream[j - k]) and $ff, int(stream[j - f - k]) and $ff));
                    end;
                end else begin
                    for j := result to result + f - 1 do begin
                        stream[j] := byte(stream[j] + paethPredictor(0, 0, 0));
                    end;
                    for j := result + f to result + lineSize - 1 do begin
                        stream[j] := byte(stream[j] + paethPredictor(int(stream[j - f]) and $ff, 0, 0));
                    end;
                end;
            end;
            end;
            unwrap(@(stream[result]), @(imageData[ofs]), total);
            inc(ofs, total);
            inc(result, lineSize);
        end;
    end;

    function ImagePNG.decodeAdam7(const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int;
    var
        data: byte_Array1d;
        adam7interlacing: int_Array1d;
        currentInterlacing: int;
        currentPassage: int;
        width: int;
        height: int;
        x0: int;
        y0: int;
        x: int;
        y: int;
        c: int;
        xstart: int;
        ystart: int;
        xinc: int;
        yinc: int;
    begin
        result := startIndex;
        adam7interlacing := toIntArray1d([$0088, $4088, $0448, $2044, $0224, $1022, $0112]);
        for currentPassage := 0 to System.length(adam7interlacing) - 1 do begin
            currentInterlacing := adam7interlacing[currentPassage];
            xstart := getXStart(currentInterlacing);
            ystart := getYStart(currentInterlacing);
            xinc := getXInc(currentInterlacing);
            yinc := getYInc(currentInterlacing);
            width := (imageWidth - xstart + xinc - 1) div xinc;
            height := (imageHeight - ystart + yinc - 1) div yinc;
            data := byte_Array1d_create(width * height * components);
            result := decodeNormal(stream, result, pixelType, data, width, height);
            c := components;
            y0 := 0;
            y := ystart;
            while y < imageHeight do begin
                x0 := 0;
                x := xstart;
                while x < imageWidth do begin
                    arraycopy(data, (y0 * width + x0) * c, imageData, (y * imageWidth + x) * c, c);
                    inc(x0);
                    inc(x, xinc);
                end;
                inc(y0);
                inc(y, yinc);
            end;
        end;
    end;

    procedure ImagePNG.unwrap1bitPP(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $03
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $07
                xor     ecx, $07
                shr     eax, cl
                and     eax, $01
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap2bitPP(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $02
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $03
                xor     ecx, $03
                lea     ecx, [ecx+ecx]
                shr     eax, cl
                and     eax, $03
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap4bitPP(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $01
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $01
                xor     ecx, $01
                lea     ecx, [ecx*4]
                shr     eax, cl
                and     eax, $0f
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap1bitPC(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $03
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $07
                xor     ecx, $07
                shr     eax, cl
                and     eax, $01
                shl     eax, $07
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap2bitPC(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $02
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $03
                xor     ecx, $03
                lea     ecx, [ecx+ecx]
                shr     eax, cl
                and     eax, $03
                shl     eax, $06
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap4bitPC(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     lea     ecx, [edx+$00]
                shr     ecx, $01
                movzx   eax, byte[esi+ecx]
                lea     ecx, [edx+$00]
                and     ecx, $01
                xor     ecx, $01
                lea     ecx, [ecx*4]
                shr     eax, cl
                and     eax, $0f
                shl     eax, $04
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap8bitPC(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                mov     ecx, [length]
                rep movsb
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.unwrap16bitPC(source, dest: Pointer; length: int); stdcall; assembler;
    asm
                push    esi
                push    edi
                cld
                mov     esi, [source]
                mov     edi, [dest]
                xor     edx, edx
                jmp     @1
        @0:     movzx   eax, byte[esi+edx*2]
                stosb
                lea     edx, [edx+$01]
        @1:     cmp     edx, [length]
                jl      @0
                pop     edi
                pop     esi
    end;

    procedure ImagePNG.init();
    var
        i: int;
        g: byte_Array1d;
        p: int_Array1d;
    begin
        useGrayscale := false;
        usePalette := false;
        useAlpha := false;
        width := 0;
        height := 0;
        components := 0;
        bitDepth := 0;
        data := nil;
        if gamma = nil then begin
            gamma := byte_Array1d_create(256);
        end;
        if palette = nil then begin
            palette := int_Array1d_create(256);
        end;
        g := gamma;
        p := palette;
        for i := $00 to $ff do begin
            g[i] := byte(i);
            p[i] := 0;
        end;
    end;

    function ImagePNG.isEmpty(): boolean;
    begin
        result := (width = 0) or (height = 0);
    end;

    function ImagePNG.getWidth(): int;
    begin
        result := width;
    end;

    function ImagePNG.getHeight(): int;
    begin
        result := height;
    end;

    procedure ImagePNG.getPixels(const dst: int_Array1d; offset, scanlength, left, top, width, height: int);
    var
        lim: int;
        len: int;
        i: int;
        j: int;
        a: int;
        b: int;
    begin
        lim := left + width;
        len := self.width;
        if (lim > len) or (lim < left) or (left < 0) or (left > len) then begin
            raise IllegalArgumentException.create('Image.getPixels: заданная область выходит за пределы изображения.');
        end;
        lim := top + height;
        len := self.height;
        if (lim > len) or (lim < top) or (top < 0) or (top > len) then begin
            raise IllegalArgumentException.create('Image.getPixels: заданная область выходит за пределы изображения.');
        end;
        if scanlength < width then begin
            raise IllegalArgumentException.create('Image.getPixels: длина линии сканирования не должна быть меньше ширины.');
        end;
        lim := offset + (height - 1) * scanlength + width;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('Image.getPixels: индекс элемента массива выходит из диапазона.');
        end;
        for j := 0 to height - 1 do begin
            a := offset + j * scanlength;
            b := ((top + j) * self.width + left) * components;
            for i := 0 to width - 1 do begin
                dst[a] := readPixel(b);
                inc(a);
                inc(b, components);
            end;
        end;
    end;

    procedure ImagePNG.loadFromStream(istream: Input);
    const
        SIGNATURE = long(-$76afb1b8f2f5e5f6);
        HEADER_START = int($49484452);
        GAMMA_START = int($67414d41);
        PALETTE_START = int($504c5445);
        TRANSPARENCY_START = int($74524e53);
        DATA_START = int($49444154);
        END_START = int($49454e44);
    type
        DecodeMethod = function (const stream: byte_Array1d; startIndex, pixelType: int; const imageData: byte_Array1d; imageWidth, imageHeight: int): int of object;
    var
        chunkLength: int;
        chunkStart: int;
        pixelType: int;
        headerHandled: boolean;
        gammaHandled: boolean;
        paletteHandled: boolean;
        transparencyHandled: boolean;
        stream: DataInput;
        decode: DecodeMethod;
        compressedData: ByteArrayOutputStream;
        compressedDataAsInterface: Output;

        procedure invalidChunkOrder();
        begin
            init();
            raise IOException.create('Ошибка в данных PNG, изображение повреждено.');
        end;

        procedure handleHeader();
        var
            w: int;
            h: int;
            bd: int;
            pt: int;
            cm: int;
            fm: int;
            im: int;
            area: long;
        begin
            if headerHandled or (chunkLength <> $0d) then begin
                invalidChunkOrder();
            end;
            w := stream.readInt(); { ширина }
            h := stream.readInt(); { высота }
            bd := stream.readByte(); { глубина цвета }
            pt := stream.readByte(); { тип пикселов }
            cm := stream.readByte(); { алгоритм сжатия }
            fm := stream.readByte(); { алгоритм фильтрации }
            im := stream.readByte(); { алгоритм отображения }
            area := zeroExtend(w) * zeroExtend(h);
            if
                (area <= 0) or (area > 1000000) or
                (
                    ((pt <> 0) or ((bd <> 1) and (bd <> 2) and (bd <> 4) and (bd <> 8) and (bd <> 16))) and
                    ((pt <> 3) or ((bd <> 1) and (bd <> 2) and (bd <> 4) and (bd <> 8))) and
                    (((pt <> 2) and (pt <> 4) and (pt <> 6)) or ((bd <> 8) and (bd <> 16)))
                ) or
                (cm <> 0) or (fm <> 0) or ((im <> 0) and (im <> 1))
            then begin
                invalidChunkOrder();
            end;
            pixelType := pt;
            useGrayscale := (pt = 0) or (pt = 4);
            usePalette := pt = 3;
            useAlpha := (pt = 4) or (pt = 6);
            width := w;
            height := h;
            components := toByteArray1d([1, 0, 3, 1, 2, 0, 4])[pt];
            bitDepth := bd;
            case im of
             0: decode := decodeNormal;
             1: decode := decodeAdam7;
            end;
            headerHandled := true;
        end;

        procedure handleGamma();
        var
            gammaValue: real;
            exponent: real;
            i: int;
            g: byte_Array1d;
        begin
            if (not headerHandled) or gammaHandled or (chunkLength <> $04) then begin
                invalidChunkOrder();
            end;
            g := gamma;
            gammaValue := toReal(stream.readInt());
            exponent := 1.0e+5 / (2.2 * gammaValue);
            for i := $00 to $ff do begin
                g[i] := byte(round(255.0 * power(toReal(i) / 255.0, exponent)));
            end;
            gammaHandled := true;
        end;

        procedure handlePalette();
        var
            r: int;
            g: int;
            b: int;
            i: int;
        begin
            if (not headerHandled) or paletteHandled or (chunkLength > $300) or (chunkLength < 0) or ((chunkLength mod 3) <> 0) then begin
                invalidChunkOrder();
            end;
            for i := 0 to (chunkLength div 3) - 1 do begin
                r := stream.readUnsignedByte();
                g := stream.readUnsignedByte();
                b := stream.readUnsignedByte();
                palette[i] := int($ff000000) + (r shl $10) + (g shl $08) + b;
            end;
            paletteHandled := true;
        end;

        procedure handleTransparency();
        var
            i: int;
            p: int_Array1d;
        begin
            if (not headerHandled) or transparencyHandled or (chunkLength > $100) or (chunkLength < 0) then begin
                invalidChunkOrder();
            end;
            if usePalette then begin
                p := palette;
                for i := 0 to chunkLength - 1 do begin
                    p[i] := (p[i] and $00ffffff) + (stream.readUnsignedByte() shl $18);
                end;
            end else begin
                { прозрачность по цвету не поддерживается }
                stream.seek(zeroExtend(chunkLength));
            end;
            transparencyHandled := true;
        end;

        procedure handleData();
        begin
            if (not headerHandled) or (chunkLength < 0) then begin
                invalidChunkOrder();
            end;
            copyBytes(stream, compressedDataAsInterface, zeroExtend(chunkLength));
        end;

        procedure handleEnd();
        begin
            if (not headerHandled) or (chunkLength <> 0) then begin
                invalidChunkOrder();
            end;
            data := byte_Array1d_create(width * height * components);
            decode(Zlib.decompress(compressedData.toByteArray()), 0, pixelType, data, width, height);
        end;

        procedure handleSome();
        begin
            stream.seek(zeroExtend(chunkLength));
        end;

    begin
        stream := DataInputStream.create(istream);
        if stream.readLong() <> SIGNATURE then begin
            raise IOException.create('Image.loadFromStream: неверная сигнатура.');
        end;
        init();
        headerHandled := false;
        gammaHandled := false;
        paletteHandled := false;
        transparencyHandled := false;
        compressedData := ByteArrayOutputStream.create();
        compressedDataAsInterface := compressedData;
        repeat
            chunkLength := stream.readInt();
            chunkStart := stream.readInt();
            case chunkStart of
            HEADER_START:
                handleHeader();
            GAMMA_START:
                handleGamma();
            PALETTE_START:
                handlePalette();
            TRANSPARENCY_START:
                handleTransparency();
            DATA_START:
                handleData();
            END_START:
                handleEnd();
            else
                handleSome();
            end;
            stream.seek(4); { игнорируем (пропускаем) контрольную сумму }
        until chunkStart = END_START;
    end;
{%endregion}

end.

