1# This file is part of Xpra.
2# Copyright (C) 2021 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
5
6#cython: wraparound=False
7
8from xpra.log import Logger
9log = Logger("decoder", "spng")
10
11from libc.stdint cimport uintptr_t, uint32_t, uint8_t
12from xpra.buffers.membuf cimport getbuf, MemBuf #pylint: disable=syntax-error
13from xpra.util import envint
14
15MAX_SIZE = envint("XPRA_SPNG_MAX_SIZE", 8192*8192)
16
17
18cdef extern from "Python.h":
19    int PyObject_GetBuffer(object obj, Py_buffer *view, int flags)
20    void PyBuffer_Release(Py_buffer *view)
21    int PyBUF_ANY_CONTIGUOUS
22
23cdef extern from "spng.h":
24    int SPNG_VERSION_MAJOR
25    int SPNG_VERSION_MINOR
26    int SPNG_VERSION_PATCH
27
28    int SPNG_DECODE_TRNS
29
30    enum spng_format:
31        SPNG_FMT_RGBA8
32        SPNG_FMT_RGBA16
33        SPNG_FMT_RGB8
34
35    enum spng_color_type:
36        SPNG_COLOR_TYPE_GRAYSCALE
37        SPNG_COLOR_TYPE_TRUECOLOR
38        SPNG_COLOR_TYPE_INDEXED
39        SPNG_COLOR_TYPE_GRAYSCALE_ALPHA
40        SPNG_COLOR_TYPE_TRUECOLOR_ALPHA
41
42    ctypedef struct spng_ctx:
43        pass
44
45    cdef struct spng_ihdr:
46        uint32_t height
47        uint32_t width
48        uint8_t bit_depth
49        uint8_t color_type
50        uint8_t compression_method
51        uint8_t filter_method
52        uint8_t interlace_method
53
54    const char *spng_version_string()
55    const char *spng_strerror(int err)
56    spng_ctx *spng_ctx_new(int flags)
57    void spng_ctx_free(spng_ctx *ctx)
58    int spng_get_ihdr(spng_ctx *ctx, spng_ihdr *ihdr)
59    int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size) nogil
60    int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len) nogil
61    int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags) nogil
62
63
64COLOR_TYPE_STR = {
65    SPNG_COLOR_TYPE_GRAYSCALE       : "GRAYSCALE",
66    SPNG_COLOR_TYPE_TRUECOLOR       : "TRUECOLOR",
67    SPNG_COLOR_TYPE_INDEXED         : "INDEXED",
68    SPNG_COLOR_TYPE_GRAYSCALE_ALPHA : "GRAYSCALE_ALPHA",
69    SPNG_COLOR_TYPE_TRUECOLOR_ALPHA : "TRUECOLOR_ALPHA",
70    }
71
72
73def get_version():
74    return (SPNG_VERSION_MAJOR, SPNG_VERSION_MINOR, SPNG_VERSION_PATCH)
75
76def get_encodings():
77    return ("png", )
78
79def get_error_str(int r):
80    s = spng_strerror(r)
81    return str(s)
82
83def check_error(int r, msg):
84    if r:
85        log_error(r, msg)
86    return r
87
88def log_error(int r, msg):
89    log.error("Error: %s", msg)
90    log.error(" code %i: %s", r, get_error_str(r))
91
92def decompress(data):
93    cdef spng_ctx *ctx = spng_ctx_new(0)
94    if ctx==NULL:
95        raise Exception("failed to instantiate an spng context")
96
97    cdef Py_buffer py_buf
98    if PyObject_GetBuffer(data, &py_buf, PyBUF_ANY_CONTIGUOUS):
99        spng_ctx_free(ctx)
100        raise Exception("failed to read compressed data from %s" % type(data))
101
102    cdef int r
103    def close():
104        PyBuffer_Release(&py_buf)
105        spng_ctx_free(ctx)
106
107    if check_error(spng_set_png_buffer(ctx, py_buf.buf, py_buf.len),
108                   "failed to set png buffer"):
109        close()
110        return None
111
112    cdef spng_ihdr ihdr
113    if check_error(spng_get_ihdr(ctx, &ihdr),
114                   "failed to get ihdr"):
115        close()
116        return None
117
118    log("ihdr: %ix%i-%i, color-type=%s, compression-method=%#x, filter-method=%#x, interlace-method=%#x",
119        ihdr.width, ihdr.height, ihdr.bit_depth, COLOR_TYPE_STR.get(ihdr.color_type, ihdr.color_type),
120        ihdr.compression_method, ihdr.filter_method, ihdr.interlace_method)
121
122    cdef int flags = 0
123    cdef size_t out_size
124    cdef int fmt
125    if ihdr.color_type==SPNG_COLOR_TYPE_TRUECOLOR:
126        fmt = SPNG_FMT_RGB8
127        rgb_format = "RGB"
128    elif ihdr.color_type==SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
129        fmt = SPNG_FMT_RGBA8
130        rgb_format = "RGBA"
131        flags = SPNG_DECODE_TRNS
132    else:
133        raise ValueError("cannot handle color type %s" % COLOR_TYPE_STR.get(ihdr.color_type, ihdr.color_type))
134
135    if check_error(spng_decoded_image_size(ctx, fmt, &out_size),
136                   "failed to get decoded image size"):
137        close()
138        return None
139    if out_size>MAX_SIZE:
140        log.error("Error: spng image size %i is too big", out_size)
141        log.error(" maximum size supported is %i", MAX_SIZE)
142        close()
143        return None
144
145    cdef MemBuf membuf = getbuf(out_size)
146    cdef uintptr_t ptr = <uintptr_t> membuf.get_mem()
147    with nogil:
148        r = spng_decode_image(ctx, <void *> ptr, out_size, fmt, flags)
149    if check_error(r, "failed to decode image"):
150        close()
151        return None
152
153    close()
154    return memoryview(membuf), rgb_format, ihdr.width, ihdr.height
155
156
157def selftest(full=False):
158    log("spng version %s selftest" % (get_version(),))
159    import binascii
160    from xpra.codecs.codec_checks import TEST_PICTURES  #pylint: disable=import-outside-toplevel
161    for hexdata in TEST_PICTURES["png"]:
162        cdata = binascii.unhexlify(hexdata)
163        assert decompress(cdata)
164