From 829f06693015e6cd5e2c5916dca88dd251a4f1d3 Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Mon, 18 May 2026 23:29:17 +0200 Subject: [PATCH 1/6] Update stb_image.h from v2.27 to v2.30 Original code present on: https://github.com/nothings/stb/blob/master/stb_image.h --- src/stb_image.h | 487 ++++++++++++++++++++++++++++-------------------- 1 file changed, 289 insertions(+), 198 deletions(-) diff --git a/src/stb_image.h b/src/stb_image.h index d60371b..9eedabe 100644 --- a/src/stb_image.h +++ b/src/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,9 @@ LICENSE RECENT REVISION HISTORY: + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings @@ -108,7 +111,7 @@ RECENT REVISION HISTORY: Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 Brad Weinberger Matvey Cherevko github:mosra @@ -140,7 +143,7 @@ RECENT REVISION HISTORY: // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) +// stbi_image_free(data); // // Standard parameters: // int *x -- outputs image width in pixels @@ -635,7 +638,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #endif #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; @@ -1063,6 +1066,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) } #endif +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -1985,9 +2005,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) int i,j,k=0; unsigned int code; // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } h->size[k] = 0; // compute actual symbols (from jpeg spec) @@ -2112,6 +2135,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol @@ -2130,6 +2155,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) unsigned int k; int sgn; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) k = stbi_lrot(j->code_buffer, n); @@ -2144,6 +2170,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; @@ -2155,6 +2182,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; @@ -2192,8 +2220,10 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec @@ -2207,6 +2237,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location @@ -2246,8 +2277,10 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient @@ -2282,6 +2315,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; @@ -3102,6 +3136,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m) sizes[i] = stbi__get8(z->s); n += sizes[i]; } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; @@ -3351,6 +3386,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { @@ -3367,25 +3424,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } + j->marker = stbi__skip_jpeg_junk_at_end(j); // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); } else { - if (!stbi__process_marker(j, m)) return 0; + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); } - m = stbi__get_marker(j); } if (j->progressive) stbi__jpeg_finish(j); @@ -3976,6 +4030,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); @@ -3989,6 +4044,7 @@ static int stbi__jpeg_test(stbi__context *s) int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); @@ -4014,6 +4070,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); @@ -4121,6 +4178,7 @@ typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; + int hit_zeof_once; stbi__uint32 code_buffer; char *zout; @@ -4187,9 +4245,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); } - stbi__fill_bits(a); } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { @@ -4254,17 +4323,25 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) int len,dist; if (z == 256) { a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } return 1; } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { + if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } @@ -4408,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; + a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); @@ -4563,9 +4641,8 @@ enum { STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first }; static stbi_uc first_row_filter[5] = @@ -4574,29 +4651,56 @@ static stbi_uc first_row_filter[5] = STBI__F_sub, STBI__F_none, STBI__F_avg_first, - STBI__F_paeth_first + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); + int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later @@ -4608,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, @@ -4617,189 +4724,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; int filter = *raw++; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; } - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } + raw += nk; - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - + // expand bits to bytes first if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; } - if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; } } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } + STBI_FREE(filter_buf); + if (!all_ok) return 0; + return 1; } @@ -4955,7 +5010,7 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; -STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; stbi__unpremultiply_on_load_set = 1; @@ -5064,14 +5119,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS } + // even with SCAN_header, have to scan to see if we have a tRNS break; } @@ -5103,10 +5157,14 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; @@ -5115,7 +5173,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; @@ -5498,8 +5562,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { - if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); } } @@ -7187,12 +7265,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re // Run value = stbi__get8(s); count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } @@ -7446,10 +7524,17 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; @@ -7486,6 +7571,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c) while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); } return value; @@ -7516,9 +7603,13 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value From c2e101adb62b35852ddb5c03cf32f221118b9044 Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Mon, 18 May 2026 23:30:38 +0200 Subject: [PATCH 2/6] Update copyright year to 2026 --- debian/copyright | 5 +++-- src/main.cpp | 2 +- src/main.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/debian/copyright b/debian/copyright index 7d26426..ff62a30 100644 --- a/debian/copyright +++ b/debian/copyright @@ -2,7 +2,7 @@ Format: http://dep.debian.net/deps/dep5 Upstream-Name: visualization.matrix Files: * -Copyright: 2015-2021 Team Kodi +Copyright: 2015-2026 Team Kodi License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,7 +22,8 @@ License: GPL-2+ Files: debian/* -Copyright: 2013 wsnipex +Copyright: 2015-2026 Team Kodi + 2013 wsnipex License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index 013f8ef..7c10db9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2021 Team Kodi + * Copyright (C) 2005-2026 Team Kodi * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSE.md for more information. diff --git a/src/main.h b/src/main.h index 2d70c32..e81d9e1 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2021 Team Kodi + * Copyright (C) 2005-2026 Team Kodi * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSE.md for more information. From 173e8ef55cab0e377184f61005d0d0898908db3f Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Mon, 18 May 2026 23:36:29 +0200 Subject: [PATCH 3/6] update dependency kissfft to version 131.2.0 (22. Oct. 2025) No code changes on our used parts, only to have that matching to current version about. --- lib/kissfft/kiss_fft_log.h | 2 +- lib/kodi-kissfft-note.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kissfft/kiss_fft_log.h b/lib/kissfft/kiss_fft_log.h index b5b631a..5012474 100644 --- a/lib/kissfft/kiss_fft_log.h +++ b/lib/kissfft/kiss_fft_log.h @@ -33,4 +33,4 @@ -#endif /* kiss_fft_log_h */ \ No newline at end of file +#endif /* kiss_fft_log_h */ diff --git a/lib/kodi-kissfft-note.txt b/lib/kodi-kissfft-note.txt index 015c25c..262ab4d 100644 --- a/lib/kodi-kissfft-note.txt +++ b/lib/kodi-kissfft-note.txt @@ -1,2 +1,2 @@ kissfft from https://github.com/mborgerding/kissfft -Sync to version 131.1.0 (16. Feb. 2021) +Sync to version 131.2.0 (22. Oct. 2025) From 043e2d91bdcbbac4a71c1e45d5d2757c59364781 Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Mon, 18 May 2026 23:36:53 +0200 Subject: [PATCH 4/6] Implement clang cleanup and perform on add-on sources --- .clang-format | 93 ++++++++++++++++++++ src/main.cpp | 236 +++++++++++++++++++++++++++++++------------------- src/main.h | 35 +++++--- 3 files changed, 265 insertions(+), 99 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ff2c115 --- /dev/null +++ b/.clang-format @@ -0,0 +1,93 @@ +--- +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '' + Priority: 6 + - Regex: '(["/]PlatformDefs|"(system|system_gl|system_egl))\.h"' + Priority: 5 + - Regex: '"platform/[^/]+/' + Priority: 2 + - Regex: '^<[a-z0-9_]+>$' + Priority: 3 + - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$' + Priority: 3 + - Regex: '^<' + Priority: 4 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '$' +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60000 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/src/main.cpp b/src/main.cpp index 7c10db9..62cafbb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,11 +13,9 @@ #define STBI_ONLY_JPEG #define STBI_ONLY_PNG #define STBI_ONLY_BMP -#include "stb_image.h" #include "kodi/Filesystem.h" #include "kodi/General.h" - - +#include "stb_image.h" #define _USE_MATH_DEFINES #include @@ -46,26 +44,24 @@ struct Preset // NOTE: With "#if defined(HAS_GL)" the use of some shaders is avoided // as they can cause problems on weaker systems. -const std::vector g_presets = -{ - {"Kodi", 30100, "logo.frag.glsl", 99, 0, 1, -1}, - {"Album", 30101, "album.frag.glsl", 99, -1, 1, 2}, - {"Rain only", 30102, "nologo.frag.glsl", 99, -1, 1, -1}, - {"Rain with waveform", 30103, "nologowf.frag.glsl", 99, -1, 1, -1}, - {"Rain with waveform envelope", 30104, "nologowfenv.frag.glsl", 99, -1, 1, -1}, - {"Clean", 30105, "clean.frag.glsl", 99, -1, -1, -1}, - {"Clean with waveform", 30106, "cleanwf.frag.glsl", 99, -1, -1, -1}, - {"Clean with waveform envelope", 30107, "cleanwfenv.frag.glsl", 99, -1, -1, -1}, +const std::vector g_presets = { + {"Kodi", 30100, "logo.frag.glsl", 99, 0, 1, -1}, + {"Album", 30101, "album.frag.glsl", 99, -1, 1, 2}, + {"Rain only", 30102, "nologo.frag.glsl", 99, -1, 1, -1}, + {"Rain with waveform", 30103, "nologowf.frag.glsl", 99, -1, 1, -1}, + {"Rain with waveform envelope", 30104, "nologowfenv.frag.glsl", 99, -1, 1, -1}, + {"Clean", 30105, "clean.frag.glsl", 99, -1, -1, -1}, + {"Clean with waveform", 30106, "cleanwf.frag.glsl", 99, -1, -1, -1}, + {"Clean with waveform envelope", 30107, "cleanwfenv.frag.glsl", 99, -1, -1, -1}, }; -const std::vector g_fileTextures = -{ - "logo.png", - "noise.png", +const std::vector g_fileTextures = { + "logo.png", + "noise.png", }; -std::string fsCommonFunctionsLowPower = -R"functions(float h11(float p) +std::string fsCommonFunctionsLowPower = + R"functions(float h11(float p) { return fract(.13 * p + 217943.37373737 / (p + 0.31)); } @@ -99,8 +95,8 @@ vec2 getUV() )functions"; -std::string fsCommonFunctionsNormal = -R"functions(float h11(float p) +std::string fsCommonFunctionsNormal = + R"functions(float h11(float p) { return fract(20.12345+sin(p*cRNDSEED1)*cRNDSEED2); } @@ -157,9 +153,12 @@ CVisualizationMatrix::CVisualizationMatrix() } else { - if (Height() <= 900) m_dotSize = 3.; - else if (Height() <= 1500) m_dotSize = 4.; - else m_dotSize = 5.; + if (Height() <= 900) + m_dotSize = 3.; + else if (Height() <= 1500) + m_dotSize = 4.; + else + m_dotSize = 5.; } m_fallSpeed = static_cast(kodi::addon::GetSettingInt("fallspeed")) * .01; m_distortThreshold = static_cast(kodi::addon::GetSettingInt("distortthreshold")) * .005; @@ -168,14 +167,18 @@ CVisualizationMatrix::CVisualizationMatrix() m_dotColor.green = static_cast(kodi::addon::GetSettingInt("green")) / 255.f; m_dotColor.blue = static_cast(kodi::addon::GetSettingInt("blue")) / 255.f; m_lowpower = kodi::addon::GetSettingBoolean("lowpower"); - m_noiseFluctuation = m_lowpower ? (static_cast(kodi::addon::GetSettingInt("noisefluctuation")) * 0.0002f)/m_fallSpeed * 0.25f : (static_cast(kodi::addon::GetSettingInt("noisefluctuation")) * 0.0004f)/m_fallSpeed * 0.25f; + m_noiseFluctuation = + m_lowpower ? (static_cast(kodi::addon::GetSettingInt("noisefluctuation")) * 0.0002f) / + m_fallSpeed * 0.25f + : (static_cast(kodi::addon::GetSettingInt("noisefluctuation")) * 0.0004f) / + m_fallSpeed * 0.25f; m_crtCurve = kodi::addon::GetSettingBoolean("crtcurve"); m_lastAlbumChange = 0.0; } CVisualizationMatrix::~CVisualizationMatrix() { - delete [] m_pcm; + delete[] m_pcm; free(m_kissCfg); } @@ -190,17 +193,17 @@ void CVisualizationMatrix::Render() } } -bool CVisualizationMatrix::Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, const std::string& szSongName) +bool CVisualizationMatrix::Start(int iChannels, + int iSamplesPerSec, + int iBitsPerSample, + const std::string& szSongName) { - kodi::Log(ADDON_LOG_DEBUG, "Start %i %i %i %s\n", iChannels, iSamplesPerSec, iBitsPerSample, szSongName.c_str()); + kodi::Log(ADDON_LOG_DEBUG, "Start %i %i %i %s\n", iChannels, iSamplesPerSec, iBitsPerSample, + szSongName.c_str()); //background vertex - static const GLfloat vertex_data[] = - { - -1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, - 1.0,-1.0, 1.0, 1.0, - -1.0,-1.0, 1.0, 1.0, + static const GLfloat vertex_data[] = { + -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, }; // Upload vertex data to a buffer @@ -226,7 +229,6 @@ void CVisualizationMatrix::Stop() glDeleteBuffers(1, &m_state.vertex_buffer); } - void CVisualizationMatrix::AudioData(const float* pAudioData, size_t iAudioDataLength) { WriteToBuffer(pAudioData, iAudioDataLength, 2); @@ -242,9 +244,11 @@ void CVisualizationMatrix::AudioData(const float* pAudioData, size_t iAudioDataL out[0].i = 0; - SmoothingOverTime(m_magnitudeBuffer.data(), m_magnitudeBuffer.data(), out, NUM_BANDS, SMOOTHING_TIME_CONSTANT, AUDIO_BUFFER); + SmoothingOverTime(m_magnitudeBuffer.data(), m_magnitudeBuffer.data(), out, NUM_BANDS, + SMOOTHING_TIME_CONSTANT, AUDIO_BUFFER); - const double rangeScaleFactor = MAX_DECIBELS == MIN_DECIBELS ? 1 : (1.0 / (MAX_DECIBELS - MIN_DECIBELS)); + const double rangeScaleFactor = + MAX_DECIBELS == MIN_DECIBELS ? 1 : (1.0 / (MAX_DECIBELS - MIN_DECIBELS)); for (unsigned int i = 0; i < NUM_BANDS; i++) { float linearValue = m_magnitudeBuffer[i]; @@ -286,7 +290,7 @@ bool CVisualizationMatrix::PrevPreset() bool CVisualizationMatrix::LoadPreset(int select) { - kodi::Log(ADDON_LOG_DEBUG, "Loading preset %i\n",select); + kodi::Log(ADDON_LOG_DEBUG, "Loading preset %i\n", select); m_currentPreset = select % g_presets.size(); Launch(m_currentPreset); UpdateAlbumart(); @@ -334,29 +338,35 @@ bool CVisualizationMatrix::UpdateAlbumart(const std::string& albumart) { m_albumArt = albumart; - kodi::Log(ADDON_LOG_DEBUG, "Updating album art %s\n",albumart.c_str()); + kodi::Log(ADDON_LOG_DEBUG, "Updating album art %s\n", albumart.c_str()); if (g_presets[m_currentPreset].channel[3] != 2) { return false; } std::string thumb = kodi::vfs::GetCacheThumbName(albumart.c_str()); - thumb = thumb.substr(0,8); - std::string special = std::string("special://thumbnails/") + thumb.c_str()[0] + std::string("/") + thumb.c_str(); + thumb = thumb.substr(0, 8); + std::string special = + std::string("special://thumbnails/") + thumb.c_str()[0] + std::string("/") + thumb.c_str(); if (kodi::vfs::FileExists(special + std::string(".png"))) { - m_channelTextures[3] = CreateTexture(kodi::vfs::TranslateSpecialProtocol(special + std::string(".png")), GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); + m_channelTextures[3] = + CreateTexture(kodi::vfs::TranslateSpecialProtocol(special + std::string(".png")), GL_RGBA, + GL_LINEAR, GL_CLAMP_TO_EDGE); return true; } else if (kodi::vfs::FileExists(special + std::string(".jpg"))) { - m_channelTextures[3] = CreateTexture(kodi::vfs::TranslateSpecialProtocol(special + std::string(".jpg")), GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); + m_channelTextures[3] = + CreateTexture(kodi::vfs::TranslateSpecialProtocol(special + std::string(".jpg")), GL_RGBA, + GL_LINEAR, GL_CLAMP_TO_EDGE); return true; } - m_channelTextures[3] = CreateTexture(kodi::addon::GetAddonPath("resources/textures/logo.png"), GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); - + m_channelTextures[3] = CreateTexture(kodi::addon::GetAddonPath("resources/textures/logo.png"), + GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); + return false; } @@ -370,9 +380,14 @@ void CVisualizationMatrix::RenderTo(GLuint shader, GLuint effect_fb) GLuint h = Height(); if (m_state.fbwidth && m_state.fbheight) w = m_state.fbwidth, h = m_state.fbheight; - int64_t intt = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0 * m_fallSpeed) - m_initialTime; + int64_t intt = + static_cast(std::chrono::duration( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() * + 1000.0 * m_fallSpeed) - + m_initialTime; if (m_bitsPrecision) - intt &= (1<(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); - float delta = static_cast(logotimer - m_lastAlbumChange)*0.6f; - GLfloat r = std::max(static_cast(sin(delta)),0.0f)*0.7f; - GLfloat g = std::max(static_cast(sin(delta - 1.0f)),0.0f)*0.7f; - GLfloat b = std::max(static_cast(sin(delta - 2.0f)),0.0f)*0.7f; + double logotimer = std::chrono::duration( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + float delta = static_cast(logotimer - m_lastAlbumChange) * 0.6f; + GLfloat r = std::max(static_cast(sin(delta)), 0.0f) * 0.7f; + GLfloat g = std::max(static_cast(sin(delta - 1.0f)), 0.0f) * 0.7f; + GLfloat b = std::max(static_cast(sin(delta - 2.0f)), 0.0f) * 0.7f; glUniform3f(m_attrAlbumRGBLoc, r, g, b); if (m_lastAlbumChange == 0.0) { @@ -402,15 +419,20 @@ void CVisualizationMatrix::RenderTo(GLuint shader, GLuint effect_fb) } if (logotimer - m_lastAlbumChange >= 10.) { - m_albumX = static_cast(std::fmod(logotimer * 1234., 1.) * (static_cast(Width())/static_cast(Height()) + 1.) - 1.); + m_albumX = static_cast( + std::fmod(logotimer * 1234., 1.) * + (static_cast(Width()) / static_cast(Height()) + 1.) - + 1.); m_albumY = static_cast(std::fmod(logotimer * 7654., 1.)); m_lastAlbumChange = logotimer; m_AlbumNeedsUpload = true; } if (m_AlbumNeedsUpload) { - glUniform3f(m_attrAlbumPositionLoc, m_albumX, m_albumY, 2.0f);//FIXME: proper framing, the album can reach over the edge of the screen - m_AlbumNeedsUpload = true;//FIXME: limit upload to the actual album shader + glUniform3f( + m_attrAlbumPositionLoc, m_albumX, m_albumY, + 2.0f); //FIXME: proper framing, the album can reach over the edge of the screen + m_AlbumNeedsUpload = true; //FIXME: limit upload to the actual album shader } } } @@ -452,7 +474,10 @@ void CVisualizationMatrix::RenderTo(GLuint shader, GLuint effect_fb) glUseProgram(0); } -void CVisualizationMatrix::Mix(float* destination, const float* source, size_t frames, size_t channels) +void CVisualizationMatrix::Mix(float* destination, + const float* source, + size_t frames, + size_t channels) { size_t length = frames * channels; for (unsigned int i = 0; i < length; i += channels) @@ -499,9 +524,11 @@ void CVisualizationMatrix::Launch(int preset) m_usedShaderFile = kodi::addon::GetAddonPath("resources/shaders/" + g_presets[preset].file); for (int i = 0; i < 4; i++) { - if (g_presets[preset].channel[i] >= 0 && g_presets[preset].channel[i] < static_cast< int > (g_fileTextures.size())) + if (g_presets[preset].channel[i] >= 0 && + g_presets[preset].channel[i] < static_cast(g_fileTextures.size())) { - m_shaderTextures[i].texture = kodi::addon::GetAddonPath("resources/textures/" + g_fileTextures[g_presets[preset].channel[i]]); + m_shaderTextures[i].texture = kodi::addon::GetAddonPath( + "resources/textures/" + g_fileTextures[g_presets[preset].channel[i]]); } else if (g_presets[preset].channel[i] == 99) // framebuffer { @@ -518,17 +545,20 @@ void CVisualizationMatrix::Launch(int preset) // Logo if (!m_shaderTextures[1].texture.empty()) { - m_channelTextures[1] = CreateTexture(m_shaderTextures[1].texture, GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); + m_channelTextures[1] = + CreateTexture(m_shaderTextures[1].texture, GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); } // Noise if (!m_shaderTextures[2].texture.empty()) { - m_channelTextures[2] = CreateTexture(m_shaderTextures[2].texture, GL_RGBA, GL_LINEAR, GL_REPEAT); + m_channelTextures[2] = + CreateTexture(m_shaderTextures[2].texture, GL_RGBA, GL_LINEAR, GL_REPEAT); } // Album if (!m_shaderTextures[3].texture.empty()) { - m_channelTextures[3] = CreateTexture(m_shaderTextures[3].texture, GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); + m_channelTextures[3] = + CreateTexture(m_shaderTextures[3].texture, GL_RGBA, GL_LINEAR, GL_CLAMP_TO_EDGE); } m_state.fbwidth = Width(); @@ -552,11 +582,13 @@ void CVisualizationMatrix::LoadPreset(const std::string& shaderPath) { UnloadPreset(); GatherDefines(); - std::string vertMatrixShader = kodi::addon::GetAddonPath("resources/shaders/main_matrix_" GL_TYPE_STRING ".vert.glsl"); + std::string vertMatrixShader = + kodi::addon::GetAddonPath("resources/shaders/main_matrix_" GL_TYPE_STRING ".vert.glsl"); if (!m_matrixShader.LoadShaderFiles(vertMatrixShader, shaderPath) || !m_matrixShader.CompileAndLink("", "", m_defines, "")) { - kodi::Log(ADDON_LOG_ERROR, "Failed to compile matrix shaders (current file '%s')", shaderPath.c_str()); + kodi::Log(ADDON_LOG_ERROR, "Failed to compile matrix shaders (current file '%s')", + shaderPath.c_str()); return; } @@ -570,23 +602,28 @@ void CVisualizationMatrix::LoadPreset(const std::string& shaderPath) m_attrChannelLoc[2] = glGetUniformLocation(matrixShader, "iChannel2"); m_attrChannelLoc[3] = glGetUniformLocation(matrixShader, "iChannel3"); - m_state.attr_vertex_e = glGetAttribLocation(matrixShader, "vertex"); + m_state.attr_vertex_e = glGetAttribLocation(matrixShader, "vertex"); // Prepare a texture to render to glActiveTexture(GL_TEXTURE0); glGenTextures(1, &m_state.framebuffer_texture); glBindTexture(GL_TEXTURE_2D, m_state.framebuffer_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_state.fbwidth, m_state.fbheight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_state.fbwidth, m_state.fbheight, 0, GL_RGB, + GL_UNSIGNED_BYTE, 0); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Prepare a framebuffer for rendering glGenFramebuffers(1, &m_state.effect_fb); glBindFramebuffer(GL_FRAMEBUFFER, m_state.effect_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_state.framebuffer_texture, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + m_state.framebuffer_texture, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); - m_initialTime = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0); + m_initialTime = static_cast( + std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() * + 1000.0); m_initialTime += (m_initialTime % 100000); } @@ -604,15 +641,18 @@ void CVisualizationMatrix::UnloadPreset() } } -GLuint CVisualizationMatrix::CreateTexture(GLint format, unsigned int w, unsigned int h, const GLvoid* data) +GLuint CVisualizationMatrix::CreateTexture(GLint format, + unsigned int w, + unsigned int h, + const GLvoid* data) { GLuint texture = 0; glActiveTexture(GL_TEXTURE0); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -621,7 +661,13 @@ GLuint CVisualizationMatrix::CreateTexture(GLint format, unsigned int w, unsigne return texture; } -GLuint CVisualizationMatrix::CreateTexture(const GLvoid* data, GLint format, unsigned int w, unsigned int h, GLint internalFormat, GLint scaling, GLint repeat) +GLuint CVisualizationMatrix::CreateTexture(const GLvoid* data, + GLint format, + unsigned int w, + unsigned int h, + GLint internalFormat, + GLint scaling, + GLint repeat) { GLuint texture = 0; glGenTextures(1, &texture); @@ -639,20 +685,23 @@ GLuint CVisualizationMatrix::CreateTexture(const GLvoid* data, GLint format, uns return texture; } -GLuint CVisualizationMatrix::CreateTexture(const std::string& file, GLint internalFormat, GLint scaling, GLint repeat) +GLuint CVisualizationMatrix::CreateTexture(const std::string& file, + GLint internalFormat, + GLint scaling, + GLint repeat) { kodi::Log(ADDON_LOG_DEBUG, "creating texture %s\n", file.c_str()); - int width,height,n; + int width, height, n; unsigned char* image; stbi_set_flip_vertically_on_load(true); - + image = stbi_load(file.c_str(), &height, &width, &n, STBI_rgb_alpha); if (image == nullptr) { kodi::Log(ADDON_LOG_ERROR, "couldn't load image"); return 0; - } + } GLuint texture = CreateTexture(image, GL_RGBA, width, height, internalFormat, scaling, repeat); stbi_image_free(image); @@ -672,13 +721,19 @@ float CVisualizationMatrix::BlackmanWindow(float in, size_t i, size_t length) return in * (a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x)); } -void CVisualizationMatrix::SmoothingOverTime(float* outputBuffer, float* lastOutputBuffer, kiss_fft_cpx* inputBuffer, size_t length, float smoothingTimeConstant, unsigned int fftSize) +void CVisualizationMatrix::SmoothingOverTime(float* outputBuffer, + float* lastOutputBuffer, + kiss_fft_cpx* inputBuffer, + size_t length, + float smoothingTimeConstant, + unsigned int fftSize) { for (size_t i = 0; i < length; i++) { kiss_fft_cpx c = inputBuffer[i]; float magnitude = sqrt(c.r * c.r + c.i * c.i) / (float)fftSize; - outputBuffer[i] = smoothingTimeConstant * lastOutputBuffer[i] + (1.0 - smoothingTimeConstant) * magnitude; + outputBuffer[i] = + smoothingTimeConstant * lastOutputBuffer[i] + (1.0 - smoothingTimeConstant) * magnitude; } } @@ -691,7 +746,7 @@ float CVisualizationMatrix::LinearToDecibels(float linear) int CVisualizationMatrix::DetermineBitsPrecision() { - m_state.fbwidth = 32, m_state.fbheight = 26*10; + m_state.fbwidth = 32, m_state.fbheight = 26 * 10; LoadPreset(kodi::addon::GetAddonPath("resources/shaders/main_test.frag.glsl")); RenderTo(m_matrixShader.ProgramHandle(), m_state.effect_fb); glFinish(); @@ -702,9 +757,9 @@ int CVisualizationMatrix::DetermineBitsPrecision() int bits = 0; unsigned char b = 0; - for (int j=0; j>1))]; + unsigned char c = buffer[4 * (j * m_state.fbwidth + (m_state.fbwidth >> 1))]; if (c && !b) bits++; b = c; @@ -740,19 +795,24 @@ void CVisualizationMatrix::GatherDefines() m_defines += "const float cVIGNETTEINTENSITY = 0.05;\n"; m_defines += "const float cDotSize = " + std::to_string(m_dotSize) + ";\n"; - m_defines += "const float cColumns = " + std::to_string(static_cast(Width())/(m_dotSize*2.0)) + ";\n"; + m_defines += + "const float cColumns = " + std::to_string(static_cast(Width()) / (m_dotSize * 2.0)) + + ";\n"; m_defines += "const float cNoiseFluctuation = " + std::to_string(m_noiseFluctuation) + ";\n"; m_defines += "const float cDistortThreshold = " + std::to_string(m_distortThreshold) + ";\n"; m_defines += "const float cRainHighlights = " + std::to_string(m_rainHighlights) + ";\n"; - m_defines += "const vec3 cColor = vec3(" + std::to_string(m_dotColor.red) + "," + std::to_string(m_dotColor.green) + "," + std::to_string(m_dotColor.blue) + ");\n"; + m_defines += "const vec3 cColor = vec3(" + std::to_string(m_dotColor.red) + "," + + std::to_string(m_dotColor.green) + "," + std::to_string(m_dotColor.blue) + ");\n"; if (m_state.fbwidth && m_state.fbheight) { - m_defines += "const vec2 cResolution = vec2(" + std::to_string(m_state.fbwidth) + "," + std::to_string(m_state.fbheight) + ");\n"; + m_defines += "const vec2 cResolution = vec2(" + std::to_string(m_state.fbwidth) + "," + + std::to_string(m_state.fbheight) + ");\n"; } else { - m_defines += "const vec2 cResolution = vec2(" + std::to_string(Width()) + ".," + std::to_string(Height()) + ".);\n"; + m_defines += "const vec2 cResolution = vec2(" + std::to_string(Width()) + ".," + + std::to_string(Height()) + ".);\n"; } m_defines += "uniform sampler2D iChannel0;\n"; @@ -767,7 +827,7 @@ void CVisualizationMatrix::GatherDefines() m_defines += "uniform sampler2D iChannel2;\n"; m_defines += "#define dNoise\n"; } - + if (g_presets[m_currentPreset].channel[3] != -1) { m_defines += "uniform sampler2D iChannel3;\n"; @@ -795,7 +855,7 @@ void CVisualizationMatrix::GatherDefines() m_defines += fsCommonFunctionsNormal; } - kodi::Log(ADDON_LOG_DEBUG, "Fragment shader header\n%s",m_defines.c_str()); + kodi::Log(ADDON_LOG_DEBUG, "Fragment shader header\n%s", m_defines.c_str()); } ADDONCREATOR(CVisualizationMatrix) // Don't touch this! diff --git a/src/main.h b/src/main.h index e81d9e1..b453b91 100644 --- a/src/main.h +++ b/src/main.h @@ -7,23 +7,25 @@ #pragma once -#include -#include -#include +#include "kissfft/kiss_fft.h" + #include +#include #include +#include +#include -#include "kissfft/kiss_fft.h" - -class ATTR_DLL_LOCAL CVisualizationMatrix - : public kodi::addon::CAddonBase - , public kodi::addon::CInstanceVisualization +class ATTR_DLL_LOCAL CVisualizationMatrix : public kodi::addon::CAddonBase, + public kodi::addon::CInstanceVisualization { public: CVisualizationMatrix(); ~CVisualizationMatrix() override; - bool Start(int channels, int samplesPerSec, int bitsPerSample, const std::string& songName) override; + bool Start(int channels, + int samplesPerSec, + int bitsPerSample, + const std::string& songName) override; void Stop() override; void AudioData(const float* audioData, size_t audioDataLength) override; void Render() override; @@ -44,10 +46,21 @@ class ATTR_DLL_LOCAL CVisualizationMatrix void UnloadPreset(); void UnloadTextures(); GLuint CreateTexture(GLint format, unsigned int w, unsigned int h, const GLvoid* data); - GLuint CreateTexture(const GLvoid* data, GLint format, unsigned int w, unsigned int h, GLint internalFormat, GLint scaling, GLint repeat); + GLuint CreateTexture(const GLvoid* data, + GLint format, + unsigned int w, + unsigned int h, + GLint internalFormat, + GLint scaling, + GLint repeat); GLuint CreateTexture(const std::string& file, GLint internalFormat, GLint scaling, GLint repeat); float BlackmanWindow(float in, size_t i, size_t length); - void SmoothingOverTime(float* outputBuffer, float* lastOutputBuffer, kiss_fft_cpx* inputBuffer, size_t length, float smoothingTimeConstant, unsigned int fftSize); + void SmoothingOverTime(float* outputBuffer, + float* lastOutputBuffer, + kiss_fft_cpx* inputBuffer, + size_t length, + float smoothingTimeConstant, + unsigned int fftSize); float LinearToDecibels(float linear); int DetermineBitsPrecision(); bool UpdateAlbumart(); From 6a009bbc774aff3c6f3e023cd9dbf97927a9df15 Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Tue, 19 May 2026 01:59:31 +0200 Subject: [PATCH 5/6] Update to visualization API 4.1.0 --- src/main.cpp | 27 ++++++++++++++------------- src/main.h | 8 +++----- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 62cafbb..3f93ca5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -193,14 +193,8 @@ void CVisualizationMatrix::Render() } } -bool CVisualizationMatrix::Start(int iChannels, - int iSamplesPerSec, - int iBitsPerSample, - const std::string& szSongName) +bool CVisualizationMatrix::Init() { - kodi::Log(ADDON_LOG_DEBUG, "Start %i %i %i %s\n", iChannels, iSamplesPerSec, iBitsPerSample, - szSongName.c_str()); - //background vertex static const GLfloat vertex_data[] = { -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, @@ -211,17 +205,13 @@ bool CVisualizationMatrix::Start(int iChannels, glBindBuffer(GL_ARRAY_BUFFER, m_state.vertex_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); - m_samplesPerSec = iSamplesPerSec; - Launch(m_currentPreset); - m_initialized = true; - return true; } -void CVisualizationMatrix::Stop() +void CVisualizationMatrix::DeInit() { m_initialized = false; - kodi::Log(ADDON_LOG_DEBUG, "Stop"); + kodi::Log(ADDON_LOG_DEBUG, "DeInit"); UnloadPreset(); UnloadTextures(); @@ -229,6 +219,17 @@ void CVisualizationMatrix::Stop() glDeleteBuffers(1, &m_state.vertex_buffer); } +bool CVisualizationMatrix::AudioStart(int iChannels, int iSamplesPerSec, int iBitsPerSample) +{ + kodi::Log(ADDON_LOG_DEBUG, "AudioStart %i %i %i", iChannels, iSamplesPerSec, iBitsPerSample); + + m_samplesPerSec = iSamplesPerSec; + Launch(m_currentPreset); + m_initialized = true; + + return true; +} + void CVisualizationMatrix::AudioData(const float* pAudioData, size_t iAudioDataLength) { WriteToBuffer(pAudioData, iAudioDataLength, 2); diff --git a/src/main.h b/src/main.h index b453b91..6532876 100644 --- a/src/main.h +++ b/src/main.h @@ -22,11 +22,9 @@ class ATTR_DLL_LOCAL CVisualizationMatrix : public kodi::addon::CAddonBase, CVisualizationMatrix(); ~CVisualizationMatrix() override; - bool Start(int channels, - int samplesPerSec, - int bitsPerSample, - const std::string& songName) override; - void Stop() override; + bool Init() override; + void DeInit() override; + bool AudioStart(int channels, int samplesPerSec, int bitsPerSample) override; void AudioData(const float* audioData, size_t audioDataLength) override; void Render() override; bool GetPresets(std::vector& presets) override; From 384deb99ec1b522aed28b83c8aa0c3a3f642a90d Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Tue, 19 May 2026 02:02:19 +0200 Subject: [PATCH 6/6] Bump to version 22.1.0-Piers due to API version change --- visualization.matrix/addon.xml.in | 2 +- visualization.matrix/changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/visualization.matrix/addon.xml.in b/visualization.matrix/addon.xml.in index 1f5308d..5d2c70c 100644 --- a/visualization.matrix/addon.xml.in +++ b/visualization.matrix/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/visualization.matrix/changelog.txt b/visualization.matrix/changelog.txt index c4f2792..f622cbc 100644 --- a/visualization.matrix/changelog.txt +++ b/visualization.matrix/changelog.txt @@ -1,3 +1,6 @@ +22.1.0 +- Kodi visualization API update to version 4.1.0 + 20.2.0 - Kodi visualization API update to version 4.0.0