; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: PNG images decoding library.
;
;  Target OS: Any
;
;  Dependencies: memory.asm; files.asm
;
;  Notes:
;_________________________________________________________________________________________
module "PNG library"


;struct TPNGChunk
;  .length dd ?
;  .type   dd ?  ; 4 bytes ascii data - chunk name. Such as 'IDAT', 'iTXt', etc.
;  .data:        ; data itself. [.lenght] bytes.
;ends

; dword check sum follows the variable length data field.

; ALL integers in PNG are big-endian!

struct TchunkIHDR
  .Width        dd ?
  .Height       dd ?
  .BitDepth     db ?
  .ColorType    db ?
  .Compression  db ?
  .Filter       db ?
  .Interlaced   db ?
ends

ctPngGrayscale          = 0     ; .BitDepth = 1, 2, 4, 8 and 16
ctPngTrueColor          = 2     ; .BitDepth = 8, 16
ctPngIndexedColor       = 3     ; .BitDepth = 1, 2, 4, 8
ctPngAlphaGrayscale     = 4     ; .BitDepth = 8, 16
ctPngAlphaTrueColor     = 6     ; .BitDepth = 8, 16


pngCTPalette  = 1
pngCTColor    = 2
pngCTAlpha    = 4


pngFilterNone    = 0
pngFilterSub     = 1
pngFilterUp      = 2
pngFilterAverage = 3
pngFilterPaeth   = 4

pngFilterCount   = 5


; Creates TImage from the compressed PNG image.
;
; Arguments:
;   .pPNGImage - pointer to memory, where compressed image resides.
;   .size      - size of the image in bytes.
;
; Returns:
;   CF=1 if error;
;   CF=0 and eax=TImage if success.

proc CreateImagePNG, .pPNGImage, .size

  .pEnd          dd ?

  .start:

  .Width         dd ?         ;
  .Height        dd ?
  .BitDepth      dd ?
  .ColorType     dd ?

  .flags         dd ?
  .pPalette      dd ?
  .PaletteCnt    dd ?

  .pData         dd ?    ; compressed data.
  .DataSize      dd ?
  .DataCapacity  dd ?

  .BitsPerPixel  dd ?
  .SampleMask    dd ?   ; bit mask of one sample.

  .BytesPerPixel dd ?   ; for the filtering algorithms. rounded up to 1 bytes always.
  .BytesPerLine  dd ?

  .end:

  .i             dd ?    ; loop counter.
  .j             dd ?

begin
        pushad

        mov     ecx, (.end - .start)/4
        lea     edi, [.start]
        xor     eax, eax
        rep stosd

        mov     esi, [.pPNGImage]
        mov     edx, [.size]
        cmp     edx, 8 + 12 + 13 + 12           ; header + IHDR + IEND the minimal size.
        jb      .error_unexpected_end

        add     edx, esi                ; the end pointer of the image.
        mov     [.pEnd], edx

; first, check the header.

        xor     ecx, ecx
        add     ecx, 4

        mov     edi, .hdrPNGImage
        repe cmpsd
        jne     .error_structure

; we are now in IHDR chunk - it is always the first after the signature!

        lea     eax, [esi-4]
        stdcall DataCRC32, eax, sizeof.TchunkIHDR + 4
        bswap   eax
        cmp     eax, [esi+sizeof.TchunkIHDR]
        jne     .error_checksum

        cmp     [esi+TchunkIHDR.Filter], 0
        jne     .error_structure        ; unsupported filter type!

        cmp     [esi+TchunkIHDR.Compression], 0
        jne     .error_structure        ; unsupported compression method!

; The following is my (johnfound) decision, as long as this library will never be used for progressive image display.
; Support of Adam-7 interlacing will cost a lot of code, work and speed. The interlaced images are bigger, so
; there is no single argument for supporting them.

        cmp     [esi+TchunkIHDR.Interlaced], 0
        jne     .error_structure        ; unsupported Interlaced images!

        movzx   ecx, [esi+TchunkIHDR.BitDepth]
        movzx   eax, [esi+TchunkIHDR.ColorType]
        mov     [.BitDepth], ecx
        mov     [.ColorType], eax

;        OutputValue "Color type:", eax, 10, 2
;        OutputValue "Bit depth:", ecx, 10, 2


        xor     edx, edx
        dec     edx
        shr     edx, cl
        not     edx
        mov     [.SampleMask], edx

        mov     edx, ecx

        test    [.ColorType], pngCTPalette
        jnz     .size_raw_ok

        test    [.ColorType], pngCTColor
        jz      .raw_color_ok

        lea     edx, [3*edx]

.raw_color_ok:
        test    [.ColorType], pngCTAlpha
        jz      .size_raw_ok

        add     edx, ecx        ; one more sample for the alpha channel.

.size_raw_ok:
        mov     [.BitsPerPixel], edx
        mov     eax, edx

        shr     eax, 3
        jnz     .bytes_per_pixel

        inc     eax

.bytes_per_pixel:
        mov     [.BytesPerPixel], eax

; Image size:
        mov     eax, [esi+TchunkIHDR.Width]
        mov     ecx, [esi+TchunkIHDR.Height]
        bswap   eax
        bswap   ecx
        mov     [.Width], eax
        mov     [.Height], ecx

        imul    eax, edx        ; line length in bits.
        add     eax, 7
        shr     eax, 3          ; align to byte
        mov     [.BytesPerLine], eax


; end of IHDR chunk:
        add     esi, sizeof.TchunkIHDR + 4

.chunk_loop:
        cmp     esi, [.pEnd]
        jae     .error_unexpected_end

        lodsd
        bswap   eax
        mov     ecx, eax                ; chunk length in bytes
        lea     edx, [eax+4]
        lea     eax, [esi+edx]

        cmp     eax, [.pEnd]
        jae     .error_unexpected_end

        stdcall DataCRC32, esi, edx
        bswap   eax
        cmp     eax, [esi+edx]
        jne     .error_checksum

        lodsd

        cmp     eax, 'PLTE'
        je      .PLTE

        cmp     eax, 'IDAT'
        je      .IDAT

        cmp     eax, 'IEND'
        je      .IEND

        cmp     eax, 'tRNS'
        je      .tRNS

; ignore chunk if unknown

        add     esi, ecx

.next_chunk:
        add     esi, 4  ; check sum
        jmp     .chunk_loop


.tRNS:
        mov     edi, [.pPalette]
        test    edi, edi
        jz      .error_structure

        cmp     ecx, [.PaletteCnt]
        ja      .error_structure

.trns_loop:
        lodsb
        mov     [edi+3], al
        add     edi, 4
        loop    .trns_loop

        jmp     .next_chunk


.PLTE:
        cmp     [.pPalette], 0
        jne     .error_structure        ; the palette can be only one!

; But for the way of processing in this library, this is not mandatory, actually.
;        cmp     [.pData], 0
;        jne     .error_structure        ; the palette must resides before the first IDAT!


        mov     eax, ecx
        cdq
        mov     ebx, 3
        div     ebx
        test    edx, edx
        jnz     .error_structure        ; the palette must contains multiple of 3 bytes.

        mov     [.PaletteCnt], eax
        mov     ecx, eax
        shl     eax, 2                  ; we need 4 bytes palette entries.

        stdcall GetMem, eax
        jc      .error_memory_allocation

        mov     [.pPalette], eax

        mov     edi, eax

.pal_loop:
        or      ah, -1          ; alpha.
        lodsb                   ; red.
        shl     eax, 8
        lodsb                   ; green.
        shl     eax, 8
        lodsb                   ; blue.

        stosd
        loop    .pal_loop

        jmp     .next_chunk

.IDAT:
        mov     edi, [.pData]
        test    edi, edi
        jnz     .buffer_allocated

        lea     eax, [ecx*2]
        mov     [.DataCapacity], eax
        stdcall GetMem, eax
        jc      .error_memory_allocation

        mov     edi, eax
        mov     [.pData], eax

.buffer_allocated:

        mov     eax, ecx
        add     eax, [.DataSize]
        cmp     eax, [.DataCapacity]
        jbe     .capacity_enough

        shl     eax, 1
        mov     [.DataCapacity], eax

        stdcall ResizeMem, edi, eax
        jc      .error_memory_allocation

        mov     [.pData], eax
        mov     edi, eax

.capacity_enough:
        add     edi, [.DataSize]
        add     [.DataSize], ecx

        rep movsb
        jmp     .next_chunk


.IEND:
; so, all data must be collected - process it.

        cmp     [.pData], 0
        je      .error_no_image


; decompressed data buffer:

        mov     edx, [.Width]
        imul    edx, [.BitsPerPixel]
        add     edx, 7
        shr     edx, 3  ; round up and compute the byte count.
        inc     edx                      ; one more byte for the filter byte.
        imul    edx, [.Height]

        stdcall GetMem, edx
        mov     edi, eax

        mov     esi, [.pData]
        mov     ecx, [.DataSize]

        add     esi, 2          ; first 2 bytes are ZLIB data format header. Ignore them.
        sub     ecx, 2+4        ; the last 4 bytes

        stdcall Inflate, edi, edx, esi, ecx
        jc      .error_decompression

        stdcall FreeMem, [.pData]
        mov     [.pData], edi
        mov     [.DataSize], edx

; Apply filters

        xor     esi, esi        ; previous line.
        mov     eax, [.Height]
        mov     [.i], eax

.outer:
        movzx   eax, byte [edi]
        inc     edi
        xor     ecx, ecx       ; x coordinate

        cmp     eax, pngFilterCount
        jae     .error_invalid_filter

        mov     edx, [.filters+4*eax]           ; filter procedure.

.inner:
        call    edx
        inc     ecx
        cmp     ecx, [.BytesPerLine]
        jne     .inner

        mov     esi, edi
        add     edi, [.BytesPerLine]
        dec     [.i]
        jnz     .outer


; convert to 32 bit TImage

        stdcall CreateImage, [.Width], [.Height]
        jc      .error_memory_allocation

        mov     [esp+4*regEAX], eax
        mov     edi, [eax+TImage.pPixels] ; the TImage pixels array.
        mov     esi, [.pData]             ; the png image pixels.

        mov     eax, [.Height]
        mov     [.i], eax

        mov     ecx, [.BitDepth]

.outer2:
        mov     eax, [.Width]
        mov     [.j], eax

        inc     esi        ; one byte for the filter.
        lodsd
        bswap   eax

        mov     ch, $20

.inner2:
        xor     edx, edx

        mov     ebx, eax
        and     ebx, [.SampleMask]

        shl     eax, cl
        sub     ch, cl
        jnz     @f

        mov     ch, $20
        lodsd
        bswap   eax

@@:
        test    [.ColorType], pngCTPalette
        jnz     .get_palette_color

        push    ecx
        mov     ch, 8
        xor     edx, edx

.pxloop1:
        or      edx, ebx
        shr     ebx, cl
        sub     ch, cl
        jge     .pxloop1

        and     edx, $ff000000
        mov     ebx, edx
        pop     ecx

        test    [.ColorType], pngCTColor
        jnz     .read_more

; duplicate gray value two more times.
        rol     edx, 8
        or      edx, ebx
        rol     edx, 8
        or      edx, ebx

        jmp     .color_ok


.read_more:
        mov     ebx, eax
        and     ebx, [.SampleMask]

        shl     eax, cl
        sub     ch, cl
        jnz     @f

        mov     ch, $20
        lodsd
        bswap   eax

@@:
        push    ecx edx
        mov     ch, 8
        mov     edx, ebx

.pxloop2:
        or      ebx, edx
        shr     edx, cl
        sub     ch, cl
        jge     .pxloop2

        and     ebx, $ff000000
        pop     edx ecx

        rol     edx, 8
        or      edx, ebx

        mov     ebx, eax
        and     ebx, [.SampleMask]

        shl     eax, cl
        sub     ch, cl
        jnz     @f

        mov     ch, $20
        lodsd
        bswap   eax

@@:
        push    ecx edx
        mov     ch, 8
        mov     edx, ebx

.pxloop3:
        or      ebx, edx
        shr     edx, cl
        sub     ch, cl
        jge     .pxloop3

        and     ebx, $ff000000
        pop     edx ecx

        rol     edx, 8
        or      edx, ebx

.color_ok:

        test    [.ColorType], pngCTAlpha
        jnz     .read_alpha

        rol     edx, 8
        or      edx, $ff000000
        jmp     .store_px


.read_alpha:
        mov     ebx, eax
        and     ebx, [.SampleMask]

        shl     eax, cl
        sub     ch, cl
        jnz     @f

        mov     ch, $20
        lodsd
        bswap   eax

@@:
        push    ecx edx
        mov     ch, 8
        mov     edx, ebx

.pxloop4:
        or      ebx, edx
        shr     edx, cl
        sub     ch, cl
        jge     .pxloop4

        and     ebx, $ff000000
        pop     edx ecx

        rol     edx, 8
        or      edx, ebx

        jmp     .store_px

.get_palette_color:

        rol     ebx, cl
        shl     ebx, 2
        add     ebx, [.pPalette]
        mov     edx, [ebx]

.store_px:

        stdcall __fix_color, edx, regEDX        ; pre-multiply alpha

        mov     [edi], edx
        add     edi, 4

        dec     [.j]
        jnz     .inner2

        movzx   eax, ch
        shr     eax, 3
        sub     esi, eax

        dec     [.i]
        jnz     .outer2

; cleanup the allocated buffers...

        stdcall FreeMem, [.pPalette]
        stdcall FreeMem, [.pData]

        clc
        popad
        return


.error_invalid_filter:
        dbrk

.error_decompression:
        dbrk

.error_no_image:
        dbrk

.error_memory_allocation:
        dbrk

.error_checksum:
        dbrk

.error_structure:
        dbrk

.error_unexpected_end:
        dbrk

        stc
        popad
        return


  .hdrPNGImage  dd  $474e5089, $0a1a0a0d, $0d000000, 'IHDR'

  .filters      dd  .none, .sub, .up, .average, .paeth


; sub filter.
.sub:
        mov     ebx, ecx
        sub     ebx, [.BytesPerPixel]
        jl      .none

        mov     al, [edi+ebx]
        add     [edi+ecx], al

.none:
        retn


; up filter
.up:
        test    esi, esi
        jz      .none

        mov     al, [esi+ecx]
        add     [edi+ecx], al

        retn


; average filter
.average:
        xor     eax, eax

        mov     ebx, ecx
        sub     ebx, [.BytesPerPixel]
        jl      .left_ok

        add     al, [edi+ebx]

.left_ok:
        test    esi, esi
        jz      .up_ok

        add     al, [esi+ecx]

.up_ok:
        rcr     al, 1          ; 9-bit average
        add     [edi+ecx], al
        retn


; paeth filter
.paeth:
        push    ecx edx

        xor     eax, eax        ; A - left

        mov     ebx, ecx
        sub     ebx, [.BytesPerPixel]
        jl      .left_ok2

        movzx   eax, byte [edi+ebx]

.left_ok2:
        xor     edx, edx        ; B - above
        xor     ebx, ebx        ; C - left, above
        test    esi, esi
        jz      .up_ok2

        movzx   edx, byte [esi+ecx]

        sub     ecx, [.BytesPerPixel]
        jl      .up_ok2

        movzx   ebx, byte [esi+ecx]

.up_ok2:
        push    ebx edx eax

        mov     ecx, eax
        add     ecx, edx
        sub     ecx, ebx        ; p

        sub     [esp], ecx       ; pa
        sub     [esp+4], ecx     ; pb
        sub     [esp+8], ecx     ; pc

.abs1:
        neg     dword [esp]
        jl      .abs1

.abs2:
        neg     dword [esp+4]
        jl      .abs2

.abs3:
        neg     dword [esp+8]
        jl      .abs3

        mov     ecx, [esp]      ; pa
        cmp     ecx, [esp+4]    ; pb
        ja      .not_a
        cmp     ecx, [esp+8]    ; pc
        jbe     .end_pp

.not_a:
        mov     ecx, [esp+4]
        cmp     ecx, [esp+8]
        ja      .not_b

        mov     eax, edx
        jmp     .end_pp

.not_b:
        mov     eax, ebx

.end_pp:
        add     esp, 12         ; free temp variables.
        pop     edx ecx

        add     [edi+ecx], al

        retn

endp




endmodule