diff --git a/CHANGELOG.md b/CHANGELOG.md index c99fe417..0fc3789e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,65 @@ ## WLED changelog +#### Build 2301240 + +- Version bump to v0.14.0-b2 "Hoshi" +- PixelArt converter (convert any image to pixel art and display it on a matrix) (PR #3042) +- various effect updates and optimisations + - added Overlay option to some effects (allows overlapping segments) + - added gradient text on Scrolling Text + - added #DDMM, #MMDD & #HHMM date and time options for Scrolling Text effect (PR #2990) + - deprecated: Dynamic Smooth, Dissolve Rnd, Solid Glitter + - optimised & enhanced loading of default values + - new effect: Distortion Waves (2D) + - 2D support for Ripple effect + - slower minimum speed for Railway effect +- DMX effect mode & segment controls (PR #2891) +- Optimisations for conditional compiles (further reduction of code size) +- better UX with effect sliders (PR #3012) +- enhanced support for ESP32 variants: C3, S2 & S3 +- usermod enhancements (PIR, Temperature, Battery (PR #2975), Analog Clock (PR #2993)) +- new usermod SHT (PR #2963) +- 2D matrix set up with gaps or irregular panels (breaking change!) (PR #2892) +- random palette smooth changes +- hex color notations in custom palettes +- allow more virtual buses +- plethora of bugfixes + ### WLED release 0.14.0-b1 #### Build 2212222 - Version bump to v0.14.0-b1 "Hoshi" -- Full changelog TBD +- 2D matrix support (including mapping 1D effects to 2D and 2D peek) +- [internal] completely rewritten Segment & WS2812FX handling code +- [internal] ability to add custom effects via usermods +- [internal] set of 2D drawing functions +- enhanced old and new 2D effects (metadata: default values) +- custom palettes (up to 10; upload palette0.json, palette1.json, ...) +- custom effect sliders and options, quick filters +- global I2C and SPI GPIO allocation (for usermods) +- usermod settings page enhancements (dropdown & info) +- asynchronous preset loading (and added "pd" JSON API call for direct preset apply) +- new usermod Boblight (PR #2917) +- new usermod PWM Outputs (PR #2912) +- new usermod Audioreactive +- new usermod Word Clock Matrix (PR #2743) +- new usermod Ping Pong Clock (PR #2746) +- new usermod ADS1115 (PR #2752) +- new usermod Analog Clock (PR #2736) +- various usermod enhancements and updates +- allow disabling pull-up resistors on buttons +- SD card support (PR #2877) +- enhanced HTTP API to support custom effect sliders & options (X1, X2, X3, M1, M2, M3) +- network debug printer (PR #2870) +- automatic UI PC mode on large displays +- removed support for upgrading from pre-0.10 (EEPROM) +- support for setting GPIO level when LEDs are off (RMT idle level, ESP32 only) (PR #2478) +- Pakistan time-zone (PKT) +- ArtPoll support +- TM1829 LED support +- experimental support for ESP32 S2, S3 and C3 +- general improvements and bugfixes ### WLED release 0.13.3 diff --git a/package-lock.json b/package-lock.json index 59c4b368..026730dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b1", + "version": "0.14.0-b2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d9468756..91d4e6e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b1", + "version": "0.14.0-b2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/tools/cdata.js b/tools/cdata.js index d01c3e35..bf5a65e8 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -220,6 +220,7 @@ function writeChunks(srcDir, specs, resultFile) { writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple'); +writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); /* writeChunks( "wled00/data", diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 8950f928..b55076c7 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -29,6 +29,7 @@ class UsermodTemperature : public Usermod { bool degC = true; // using parasite power on the sensor bool parasite = false; + int8_t parasitePin = -1; // how often do we read from sensor? unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL; // set last reading as "40 sec before boot", so first reading is taken after 20 sec @@ -53,6 +54,7 @@ class UsermodTemperature : public Usermod { static const char _enabled[]; static const char _readInterval[]; static const char _parasite[]; + static const char _parasitePin[]; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float readDallas() { @@ -94,12 +96,14 @@ class UsermodTemperature : public Usermod { DEBUG_PRINTLN(F("Requesting temperature.")); oneWire->reset(); oneWire->skip(); // skip ROM - oneWire->write(0x44,parasite); // request new temperature reading (TODO: parasite would need special handling) + oneWire->write(0x44,parasite); // request new temperature reading + if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, HIGH); // has to happen within 10us (open MOSFET) lastTemperaturesRequest = millis(); waitingForConversion = true; } void readTemperature() { + if (parasite && parasitePin >=0 ) digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) temperature = readDallas(); lastMeasurement = millis(); waitingForConversion = false; @@ -175,6 +179,12 @@ class UsermodTemperature : public Usermod { delay(25); // try to find sensor } } + if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { + pinMode(parasitePin, OUTPUT); + digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) + } else { + parasitePin = -1; + } } else { if (temperaturePin >= 0) { DEBUG_PRINTLN(F("Temperature pin allocation failed.")); @@ -321,6 +331,7 @@ class UsermodTemperature : public Usermod { top["degC"] = degC; // usermodparam top[FPSTR(_readInterval)] = readingInterval / 1000; top[FPSTR(_parasite)] = parasite; + top[FPSTR(_parasitePin)] = parasitePin; DEBUG_PRINTLN(F("Temperature config saved.")); } @@ -346,6 +357,7 @@ class UsermodTemperature : public Usermod { readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000; readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms parasite = top[FPSTR(_parasite)] | parasite; + parasitePin = top[FPSTR(_parasitePin)] | parasitePin; if (!initDone) { // first run: reading from cfg.json @@ -360,12 +372,21 @@ class UsermodTemperature : public Usermod { delete oneWire; pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); temperaturePin = newTemperaturePin; + pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); // initialise setup(); } } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_parasite)].isNull(); + return !top[FPSTR(_parasitePin)].isNull(); + } + + void appendConfigData() + { + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasite)).c_str()); + oappend(SET_F("',1,'(if no Vcc connected)');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":")); oappend(String(FPSTR(_parasitePin)).c_str()); + oappend(SET_F("',1,'(for external MOSFET)');")); // 0 is field type, 1 is actual field } uint16_t getId() @@ -379,3 +400,4 @@ const char UsermodTemperature::_name[] PROGMEM = "Temperature"; const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr"; +const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin"; diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index b8bd4f6e..de68e11b 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -184,7 +184,7 @@ class MultiRelay : public Usermod { */ MultiRelay() { const int8_t defPins[] = {MULTI_RELAY_PINS}; - for (int i=0; i (255 - SEGMENT.speed) + 15U) - { + if (SEGENV.step > (255 - SEGMENT.speed) + 15U) { SEGENV.aux0 = !SEGENV.aux0; - SEGENV.call = 0; + SEGENV.step = 0; + } else { + SEGENV.step++; } return FRAMETIME; @@ -640,9 +643,9 @@ uint16_t dissolve(uint32_t color) { * Blink several LEDs on and then off */ uint16_t mode_dissolve(void) { - return dissolve(SEGCOLOR(0)); + return dissolve(SEGMENT.check1 ? SEGMENT.color_wheel(random8()) : SEGCOLOR(0)); } -static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed;!,!;!"; +static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed,,,,Random;!,!;!"; /* @@ -1193,17 +1196,19 @@ uint16_t mode_fireworks() { const uint16_t width = strip.isMatrix ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); const uint16_t height = SEGMENT.virtualHeight(); - SEGMENT.fade_out(0); - if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); //lossless getPixelColor() + SEGMENT.fill(SEGCOLOR(1)); SEGENV.aux0 = UINT16_MAX; SEGENV.aux1 = UINT16_MAX; } + SEGMENT.fade_out(128); + bool valid1 = (SEGENV.aux0 < width*height); bool valid2 = (SEGENV.aux1 < width*height); uint32_t sv1 = 0, sv2 = 0; - if (valid1) sv1 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width) : SEGMENT.getPixelColor(SEGENV.aux0); // TODO get spark color - if (valid2) sv2 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width) : SEGMENT.getPixelColor(SEGENV.aux1); // TODO + if (valid1) sv1 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color + if (valid2) sv2 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width) : SEGMENT.getPixelColor(SEGENV.aux1); if (!SEGENV.step) SEGMENT.blur(16); if (valid1) { if (strip.isMatrix) SEGMENT.setPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid2) { if (strip.isMatrix) SEGMENT.setPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur @@ -1230,7 +1235,7 @@ uint16_t mode_rain() const uint16_t width = SEGMENT.virtualWidth(); const uint16_t height = SEGMENT.virtualHeight(); SEGENV.step += FRAMETIME; - if (SEGENV.step > SPEED_FORMULA_L) { + if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { SEGENV.step = 1; if (strip.isMatrix) { uint32_t ctemp[width]; @@ -1241,9 +1246,9 @@ uint16_t mode_rain() SEGENV.aux1 = (SEGENV.aux1 % width) + (SEGENV.aux1 / width + 1) * width; } else { //shift all leds left - uint32_t ctemp = SEGMENT.getPixelColor(0); // TODO + uint32_t ctemp = SEGMENT.getPixelColor(0); for (int i = 0; i < SEGLEN - 1; i++) { - SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // TODO + SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); } SEGMENT.setPixelColor(SEGLEN -1, ctemp); // wrap around SEGENV.aux0++; // increase spark index @@ -1585,8 +1590,7 @@ static const char _data_FX_MODE_ICU[] PROGMEM = "ICU@!,!,,,,,Overlay;!,!;!"; /* * Custom mode by Aircoookie. Color Wipe, but with 3 colors */ -uint16_t mode_tricolor_wipe(void) -{ +uint16_t mode_tricolor_wipe(void) { uint32_t cycleTime = 1000 + (255 - SEGMENT.speed)*200; uint32_t perc = strip.now % cycleTime; uint16_t prog = (perc * 65535) / cycleTime; @@ -1628,8 +1632,7 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!"; * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/TriFade.h * Modified by Aircoookie */ -uint16_t mode_tricolor_fade(void) -{ +uint16_t mode_tricolor_fade(void) { uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1); uint32_t prog = (counter * 768) >> 16; @@ -1672,8 +1675,7 @@ static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade@!;1,2,3;!"; * Creates random comets * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h */ -uint16_t mode_multi_comet(void) -{ +uint16_t mode_multi_comet(void) { uint32_t cycleTime = 10 + (uint32_t)(255 - SEGMENT.speed); uint32_t it = strip.now / cycleTime; if (SEGENV.step == it) return FRAMETIME; @@ -1711,8 +1713,7 @@ static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet"; * Running random pixels ("Stream 2") * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/RandomChase.h */ -uint16_t mode_random_chase(void) -{ +uint16_t mode_random_chase(void) { if (SEGENV.call == 0) { SEGENV.step = RGBW32(random8(), random8(), random8(), 0); SEGENV.aux0 = random16(); @@ -1754,8 +1755,7 @@ typedef struct Oscillator { /* / Oscillating bars of color, updated with standard framerate */ -uint16_t mode_oscillate(void) -{ +uint16_t mode_oscillate(void) { uint8_t numOscillators = 3; uint16_t dataSize = sizeof(oscillator) * numOscillators; @@ -1807,8 +1807,7 @@ static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; //TODO -uint16_t mode_lightning(void) -{ +uint16_t mode_lightning(void) { uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); @@ -1853,8 +1852,7 @@ static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay // Pride2015 // Animated, ever-changing rainbows. // by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 -uint16_t mode_pride_2015(void) -{ +uint16_t mode_pride_2015(void) { uint16_t duration = 10 + SEGMENT.speed; uint16_t sPseudotime = SEGENV.step; uint16_t sHue16 = SEGENV.aux0; @@ -1883,11 +1881,8 @@ uint16_t mode_pride_2015(void) uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; bri8 += (255 - brightdepth); - CRGB newcolor = CHSV( hue8, sat8, bri8); - fastled_col = CRGB(SEGMENT.getPixelColor(i)); // TODO - - nblend(fastled_col, newcolor, 64); - SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + CRGB newcolor = CHSV(hue8, sat8, bri8); + SEGMENT.blendPixelColor(i, newcolor, 64); } SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; @@ -1898,24 +1893,29 @@ static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;"; //eight colored dots, weaving in and out of sync with each other -uint16_t mode_juggle(void){ - SEGMENT.fade_out(SEGMENT.intensity); +uint16_t mode_juggle(void) { + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); //lossless getPixelColor() + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4)); + CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { - uint16_t index = 0 + beatsin88((128 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); - fastled_col = CRGB(SEGMENT.getPixelColor(index)); // TODO + uint16_t index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + fastled_col = CRGB(SEGMENT.getPixelColor(index)); fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); - SEGMENT.setPixelColor(index, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(index, fastled_col); dothue += 32; } return FRAMETIME; } -static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=16,ix=240"; +static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;;!;;sx=64,ix=128"; -uint16_t mode_palette() -{ +uint16_t mode_palette() { uint16_t counter = 0; if (SEGMENT.speed != 0) { @@ -1963,43 +1963,42 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Cycle speed;;!;;c3= // There are two main parameters you can play with to control the look and // feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used // in step 3 above) (Effect Intensity = Sparking). -uint16_t mode_fire_2012() -{ - uint16_t strips = SEGMENT.nrOfVStrips(); +uint16_t mode_fire_2012() { + const uint16_t strips = SEGMENT.nrOfVStrips(); if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed byte* heat = SEGENV.data; - uint32_t it = strip.now >> 5; //div 32 + const uint32_t it = strip.now >> 6; //div 64 struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { + const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + + // Step 1. Cool down every cell a little + for (int i = 0; i < SEGLEN; i++) { + uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random(8); + uint8_t minTemp = 0; + if (i 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } + } - // Step 3. Randomly ignite new 'sparks' of heat near the bottom - if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - heat[y] = qadd8(heat[y], random8(160,255)); - } + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() <= SEGMENT.intensity) { + uint8_t y = random8(ignition); + uint8_t boost = (32+SEGMENT.custom3*2) * (2*ignition-y) / (2*ignition); + heat[y] = qadd8(heat[y], random8(64+boost,128+boost)); } // Step 4. Map from heat cells to LED colors @@ -2012,19 +2011,20 @@ uint16_t mode_fire_2012() for (int stripNr=0; stripNr> 1)); @@ -2157,8 +2144,7 @@ uint16_t mode_noise16_2() static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!;!;!"; -uint16_t mode_noise16_3() -{ +uint16_t mode_noise16_3() { uint16_t scale = 800; // the "zoom factor" for the noise //CRGB fastled_col; SEGENV.step += (1 + SEGMENT.speed); @@ -2183,8 +2169,7 @@ static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino -uint16_t mode_noise16_4() -{ +uint16_t mode_noise16_4() { //CRGB fastled_col; uint32_t stp = (strip.now * SEGMENT.speed) >> 7; for (int i = 0; i < SEGLEN; i++) { @@ -2199,8 +2184,7 @@ static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!;!;!"; //based on https://gist.github.com/kriegsman/5408ecd397744ba0393e -uint16_t mode_colortwinkle() -{ +uint16_t mode_colortwinkle() { uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -2353,8 +2337,7 @@ static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail //Railway Crossing / Christmas Fairy lights -uint16_t mode_railway() -{ +uint16_t mode_railway() { uint16_t dur = (256 - SEGMENT.speed) * 40; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; if (SEGENV.step > dur) @@ -2829,7 +2812,7 @@ uint16_t mode_bouncing_balls(void) { balls[i].lastBounceTime = time; if (balls[i].impactVelocity < 0.015f) { - float impactVelocityStart = sqrt(-2 * gravity) * random8(5,11)/10.0f; // randomize impact velocity + float impactVelocityStart = sqrtf(-2.0f * gravity) * random8(5,11)/10.0f; // randomize impact velocity balls[i].impactVelocity = impactVelocityStart; } } else if (balls[i].height > 1.0f) { @@ -2987,7 +2970,7 @@ uint16_t mode_popcorn(void) { uint16_t peakHeight = 128 + random8(128); //0-255 peakHeight = (peakHeight * (SEGLEN -1)) >> 8; - popcorn[i].vel = sqrt(-2.0 * gravity * peakHeight); + popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); if (SEGMENT.palette) { @@ -3286,7 +3269,7 @@ uint16_t mode_exploding_fireworks(void) flare->posX = strip.isMatrix ? random16(2,cols-1) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D uint16_t peakHeight = 75 + random8(180); //0-255 peakHeight = (peakHeight * (rows -1)) >> 8; - flare->vel = sqrt(-2.0f * gravity * peakHeight); + flare->vel = sqrtf(-2.0f * gravity * peakHeight); flare->velX = strip.isMatrix ? (random8(8)-4)/32.f : 0; // no X velocity on 1D flare->col = 255; //brightness SEGENV.aux0 = 1; @@ -4844,7 +4827,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); const uint16_t dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled - const uint16_t crcBufferLen = (SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) + const uint16_t crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed CRGB *prevLeds = reinterpret_cast(SEGENV.data); @@ -4852,7 +4835,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: CRGB backgroundColor = SEGCOLOR(1); - if (SEGENV.call == 0 || strip.now - SEGMENT.step > 5000) { + if (SEGENV.call == 0) SEGMENT.setUpLeds(); + + if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { SEGENV.step = strip.now; SEGENV.aux0 = 0; random16_set_seed(millis()>>2); //seed the random generator @@ -4868,7 +4853,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black; memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen); - } else if (strip.now - SEGENV.step < FRAMETIME_FIXED * map(SEGMENT.speed,0,255,64,4)) { + } else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) { // update only when appropriate time passes (in 42 FPS slots) return FRAMETIME; } @@ -4879,20 +4864,22 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: //calculate new leds for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - colorCount colorsCount[9];//count the different colors in the 9*9 matrix - for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; //init colorsCount - //iterate through neighbors and count them and their different colors + colorCount colorsCount[9]; // count the different colors in the 3*3 matrix + for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount + + // iterate through neighbors and count them and their different colors int neighbors = 0; - for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { //iterate through 9*9 matrix + for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix + if (i==0 && j==0) continue; // ignore itself // wrap around segment int16_t xx = x+i, yy = y+j; - if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0; + if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0; if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0; - uint16_t xy = XY(xx, yy); // previous cell xy to check - // count different neighbours and colors, except the centre cell - if (xy != XY(x,y) && prevLeds[xy] != backgroundColor) { + uint16_t xy = XY(xx, yy); // previous cell xy to check + // count different neighbours and colors + if (prevLeds[xy] != backgroundColor) { neighbors++; bool colorFound = false; int k; @@ -4901,22 +4888,24 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: colorsCount[k].count++; colorFound = true; } - if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array } } // i,j // Rules of Life - uint32_t col = SEGMENT.getPixelColorXY(x,y); + uint32_t col = prevLeds[XY(x,y)]; uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0); if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation else if ((col == bgc) && (neighbors == 3)) { // Reproduction - //find dominant color and assign to cell + // find dominant color and assign it to a cell colorCount dominantColorCount = {backgroundColor, 0}; for (int i=0; i<9 && colorsCount[i].count != 0; i++) if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i]; - if (dominantColorCount.count > 0) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); //assign the dominant color + // assign the dominant color w/ a bit of randomness to avoid "gliders" + if (dominantColorCount.count > 0 && random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); + } else if ((col == bgc) && (neighbors == 2) && !random8(128)) { // Mutation + SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 255)); } // else do nothing! } //x,y @@ -5310,7 +5299,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https SEGENV.step = 0; } - float adjustHeight = (float)map(rows, 8, 32, 28, 12); + float adjustHeight = (float)map(rows, 8, 32, 28, 12); // maybe use mapf() ??? uint16_t adjScale = map(cols, 8, 64, 310, 63); /* if (SEGENV.aux1 != SEGMENT.custom1/12) { // Hacky palette rotation. We need that black. @@ -5336,7 +5325,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, qsub8( inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), - fabs((float)rows / 2 - (float)y) * adjustHeight))); + fabsf((float)rows / 2.0f - (float)y) * adjustHeight))); } } @@ -5351,7 +5340,7 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale; uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline if (!strip.isMatrix) return mode_static(); // not a 2D set-up - //const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { @@ -5361,8 +5350,8 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); - uint16_t a = strip.now / (18 - SEGMENT.speed / 16); - uint16_t x = (a / 14); + uint32_t a = strip.now / (18 - SEGMENT.speed / 16); + uint16_t x = (a / 14) % cols; uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); @@ -5788,13 +5777,13 @@ uint16_t mode_2Dfloatingblobs(void) { // change radius if needed if (blob->grow[i]) { // enlarge radius until it is >= 4 - blob->r[i] += (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05f; + blob->r[i] += (fabsf(blob->sX[i]) > fabsf(blob->sY[i]) ? fabsf(blob->sX[i]) : fabsf(blob->sY[i])) * 0.05f; if (blob->r[i] >= MIN(cols/4.f,2.f)) { blob->grow[i] = false; } } else { // reduce radius until it is < 1 - blob->r[i] -= (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05f; + blob->r[i] -= (fabsf(blob->sX[i]) > fabsf(blob->sY[i]) ? fabsf(blob->sX[i]) : fabsf(blob->sY[i])) * 0.05f; if (blob->r[i] < 1.f) { blob->grow[i] = true; } @@ -5887,20 +5876,24 @@ uint16_t mode_2Dscrollingtext(void) { ++SEGENV.aux1 &= 0xFF; // color shift SEGENV.step = millis() + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); if (!SEGMENT.check2) { - // we need it 3 times - SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color - SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color - SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++ ) + SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); } -} + } for (int i = 0; i < numberOfLetters; i++) { if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen - SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0)); + uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); + uint32_t col2 = BLACK; + if (SEGMENT.check1 && SEGMENT.palette == 0) { + col1 = SEGCOLOR(0); + col2 = SEGCOLOR(2); + } + SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, col1, col2); } return FRAMETIME; } -static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,,,Overlay;!,!;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,,Gradient,Overlay;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; //////////////////////////// @@ -6711,11 +6704,10 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil if (SEGENV.aux0 != secondHand) { // Triggered millis timing. SEGENV.aux0 = secondHand; - SEGMENT.setPixelColor(mid, CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2)); // 16-> 15 as 16 is out of bounds - CRGB color = SEGMENT.getPixelColor(mid); - SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[1*4], 0, 255, 255, 10))); // TODO - Update + CRGB color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // 16-> 15 as 16 is out of bounds + SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // TODO - Update - for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); // move to the left for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } @@ -6736,8 +6728,8 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - float my_magnitude = *(float*) um_data->u_data[5] / 4.0f; + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + float my_magnitude = *(float*)um_data->u_data[5] / 4.0f; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) if (SEGENV.call == 0) SEGMENT.fill(BLACK); @@ -6770,7 +6762,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch um_data = simulateSound(SEGMENT.soundSim); } float FFT_MajorPeak = *(float*)um_data->u_data[4]; - float volumeSmth = *(float*) um_data->u_data[0]; + float volumeSmth = *(float*)um_data->u_data[0]; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -6807,7 +6799,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch // shift the pixels one pixel up SEGMENT.setPixelColor(0, color); - for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left } return FRAMETIME; @@ -6828,8 +6820,8 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + float my_magnitude = *(float*)um_data->u_data[5] / 16.0f; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. @@ -6871,8 +6863,8 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - float volumeSmth = *(float*) um_data->u_data[0]; + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + float volumeSmth = *(float*)um_data->u_data[0]; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -6933,16 +6925,16 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - float FFT_MajorPeak = *(float*) um_data->u_data[4]; - float volumeSmth = *(float*) um_data->u_data[0]; + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + float volumeSmth = *(float*)um_data->u_data[0]; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) SEGMENT.fade_out(250); - float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling - float mySampleAvg = mapf(segmentSampleAvg*2.0, 0,32, 0, (float)SEGLEN/2.0); // map to pixels availeable in current segment + float mySampleAvg = mapf(segmentSampleAvg*2.0f, 0,32, 0, (float)SEGLEN/2.0); // map to pixels availeable in current segment int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. uint8_t gravity = 8 - SEGMENT.speed/32; @@ -7028,7 +7020,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac } frTemp -=132; // This should give us a base musical note of C3 - frTemp = fabs(frTemp * 2.1); // Fudge factors to compress octave range starting at 0 and going to 255; + frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); i = constrain(i, 0, SEGLEN-1); diff --git a/wled00/FX.h b/wled00/FX.h index 5de4aff5..43095780 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -143,7 +143,7 @@ #define FX_MODE_SAW 16 #define FX_MODE_TWINKLE 17 #define FX_MODE_DISSOLVE 18 -#define FX_MODE_DISSOLVE_RANDOM 19 +#define FX_MODE_DISSOLVE_RANDOM 19 // candidate for removal (use Dissolve with with check 3) #define FX_MODE_SPARKLE 20 #define FX_MODE_FLASH_SPARKLE 21 #define FX_MODE_HYPER_SPARKLE 22 @@ -227,7 +227,7 @@ #define FX_MODE_HEARTBEAT 100 #define FX_MODE_PACIFICA 101 #define FX_MODE_CANDLE_MULTI 102 -#define FX_MODE_SOLID_GLITTER 103 +#define FX_MODE_SOLID_GLITTER 103 // candidate for removal (use glitter) #define FX_MODE_SUNRISE 104 #define FX_MODE_PHASED 105 #define FX_MODE_TWINKLEUP 106 @@ -241,7 +241,7 @@ // #define FX_MODE_CANDY_CANE 114 // removed in 0.14! #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 -#define FX_MODE_DYNAMIC_SMOOTH 117 +#define FX_MODE_DYNAMIC_SMOOTH 117 // candidate for removal (check3 in dynamic) // new 0.14 2D effects #define FX_MODE_2DSPACESHIPS 118 //gap fill @@ -593,8 +593,9 @@ typedef struct Segment { void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color); + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0); void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); void blur1d(fract8 blur_amount); // blur all rows in 1 dimension void blur2d(fract8 blur_amount) { blur(blur_amount); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 17882fe8..ad7f3a10 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -64,6 +64,7 @@ void WS2812FX::setUpMatrix() { Segment::maxHeight = 1; panels = 0; panel.clear(); // release memory allocated by panels + resetSegments(); return; } @@ -107,8 +108,8 @@ void WS2812FX::setUpMatrix() { panel.clear(); Segment::maxWidth = _length; Segment::maxHeight = 1; - return; } + resetSegments(); } #else isMatrix = false; // no matter what config says @@ -499,13 +500,16 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM -void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color) { +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2) { if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); const int font = w*h; + CRGB col = CRGB(color); + CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); + //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= 0 || x0 < cols) && ((bits>>(j+(8-w))) & 0x01)) { // bit set & drawing on-screen - addPixelColorXY(x0, y0, color); + setPixelColorXY(x0, y0, col); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 147f625b..235c61d0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -893,7 +893,7 @@ void Segment::blur(uint8_t blur_amount) * The colours are a transition r -> g -> b -> back to r * Inspired by the Adafruit examples. */ -uint32_t Segment::color_wheel(uint8_t pos) { // TODO +uint32_t Segment::color_wheel(uint8_t pos) { if (palette) return color_from_palette(pos, false, true, 0); pos = 255 - pos; if(pos < 85) { diff --git a/wled00/data/index.css b/wled00/data/index.css index 9a2e2642..0908ed86 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -1189,7 +1189,7 @@ TD .checkmark, TD .radiomark { } .bp { - margin-bottom: 5px; + margin-bottom: 8px; } /* segment & preset wrapper */ diff --git a/wled00/data/index.js b/wled00/data/index.js index 131fd122..68a66e3e 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -772,7 +772,7 @@ function populateSegments(s) - +
@@ -1047,13 +1047,13 @@ function updateLen(s) { if (!gId(`seg${s}s`)) return; var start = parseInt(gId(`seg${s}s`).value); - var stop = parseInt(gId(`seg${s}e`).value); - var len = stop - (cfg.comp.seglen?0:start); + var stop = parseInt(gId(`seg${s}e`).value) + (cfg.comp.seglen?start:0); + var len = stop - start; if (isM) { // matrix setup let startY = parseInt(gId(`seg${s}sY`).value); - let stopY = parseInt(gId(`seg${s}eY`).value); - len *= (stopY-(cfg.comp.seglen?0:startY)); + let stopY = parseInt(gId(`seg${s}eY`).value) + (cfg.comp.seglen?startY:0); + len *= (stopY-startY); let tPL = gId(`seg${s}lbtm`); if (stop-start>1 && stopY-startY>1) { // 2D segment @@ -1662,19 +1662,23 @@ function toggleNodes() function makeSeg() { - var ns = 0; + var ns = 0, ct = 0; var lu = lowestUnused; let li = lastinfo; if (lu > 0) { - var pend = parseInt(gId(`seg${lu -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lu -1}s`).value,10):0); - if (pend < ledCount) ns = pend; + let xend = parseInt(gId(`seg${lu -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lu -1}s`).value,10):0); + if (isM) { + ns = 0; + ct = mw; + } else { + if (xend < ledCount) ns = xend; + ct = ledCount-(cfg.comp.seglen?ns:0) + } } gId('segutil').scrollIntoView({ behavior: 'smooth', block: 'start', }); - var ct = (isM?mw:ledCount)-(cfg.comp.seglen?ns:0); - //TODO: add calculation for Y in case of 2D matrix var cn = `
diff --git a/wled00/data/pixart/boxdraw.js b/wled00/data/pixart/boxdraw.js new file mode 100644 index 00000000..c000c2e6 --- /dev/null +++ b/wled00/data/pixart/boxdraw.js @@ -0,0 +1,62 @@ +function drawBoxes(inputPixelArray, widthPixels, heightPixels) { + + var w = window; + + // Get the canvas context + var ctx = canvas.getContext('2d', { willReadFrequently: true }); + + // Set the width and height of the canvas + if (w.innerHeight < w.innerWidth) { + canvas.width = Math.floor(w.innerHeight * 0.98); + } + else{ + canvas.width = Math.floor(w.innerWidth * 0.98); + } + //canvas.height = w.innerWidth; + + let pixelSize = Math.floor(canvas.width/widthPixels); + + let xOffset = (w.innerWidth - (widthPixels * pixelSize))/2 + + //Set the canvas height to fit the right number of pixelrows + canvas.height = (pixelSize * heightPixels) + 10 + + //Iterate through the matrix + for (let y = 0; y < heightPixels; y++) { + for (let x = 0; x < widthPixels; x++) { + + // Calculate the index of the current pixel + let i = (y*widthPixels) + x; + + //Gets the RGB of the current pixel + let pixel = inputPixelArray[i]; + + let pixelColor = 'rgb(' + pixel[0] + ', ' + pixel[1] + ', ' + pixel[2] + ')'; + + let textColor = 'rgb(128,128,128)'; + + // Set the fill style to the pixel color + ctx.fillStyle = pixelColor; + + //Draw the rectangle + ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + + // Draw a border on the box + ctx.strokeStyle = '#888888'; + ctx.lineWidth = 1; + ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + + //Write text to box + ctx.font = "10px Arial"; + ctx.fillStyle = textColor; + ctx.textAlign = "center"; + ctx.textBaseline = 'middle'; + ctx.fillText((pixel[4] + 1), (x * pixelSize) + (pixelSize /2), (y * pixelSize) + (pixelSize /2)); + } + } + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); + canvas.width = w.innerWidth; + ctx.putImageData(imageData, xOffset, 0); +} + diff --git a/wled00/data/pixart/favicon-16x16.png b/wled00/data/pixart/favicon-16x16.png new file mode 100644 index 00000000..feb51ca0 Binary files /dev/null and b/wled00/data/pixart/favicon-16x16.png differ diff --git a/wled00/data/pixart/favicon-32x32.png b/wled00/data/pixart/favicon-32x32.png new file mode 100644 index 00000000..a3b5cceb Binary files /dev/null and b/wled00/data/pixart/favicon-32x32.png differ diff --git a/wled00/data/pixart/favicon.ico b/wled00/data/pixart/favicon.ico new file mode 100644 index 00000000..bde8945e Binary files /dev/null and b/wled00/data/pixart/favicon.ico differ diff --git a/wled00/data/pixart/getPixelValues.js b/wled00/data/pixart/getPixelValues.js new file mode 100644 index 00000000..ffbf94a7 --- /dev/null +++ b/wled00/data/pixart/getPixelValues.js @@ -0,0 +1,320 @@ +function getPixelRGBValues(base64Image) { + httpArray = []; + fileJSON = `{"on":true,"bri":${brgh.value},"seg":{"id":${tSg.value},"i":[`; + + //Which object holds the secret to the segment ID + + let segID = 0; + if(tSg.style.display == "flex"){ + segID = tSg.value + } else { + segID = sID.value; + } + + + //const copyJSONledbutton = gId('copyJSONledbutton'); + const maxNoOfColorsInCommandSting = parseInt(cLN.value); + + let hybridAddressing = false; + let selectedIndex = -1; + + selectedIndex = frm.selectedIndex; + const formatSelection = frm.options[selectedIndex].value; + + + selectedIndex = lSS.selectedIndex; + const ledSetupSelection = lSS.options[selectedIndex].value; + + selectedIndex = cFS.selectedIndex; + let hexValueCheck = true; + if (cFS.options[selectedIndex].value == 'dec'){ + hexValueCheck = false + } + + selectedIndex = aS.selectedIndex; + let segmentValueCheck = true; //If Range or Hybrid + if (aS.options[selectedIndex].value == 'single'){ + segmentValueCheck = false + } else if (aS.options[selectedIndex].value == 'hybrid'){ + hybridAddressing = true; + } + + let curlString = '' + let haString = '' + + let colorSeparatorStart = '"'; + let colorSeparatorEnd = '"'; + if (!hexValueCheck){ + colorSeparatorStart = '['; + colorSeparatorEnd = ']'; + } + // Warnings + let hasTransparency = false; //If alpha < 255 is detected on any pixel, this is set to true in code below + let imageInfo = ''; + + // Create an off-screen canvas + var canvas = cE('canvas'); + var context = canvas.getContext('2d', { willReadFrequently: true }); + + // Create an image element and set its src to the base64 image + var image = new Image(); + image.src = base64Image; + + // Wait for the image to load before drawing it onto the canvas + image.onload = function() { + + let scalePath = scDiv.children[0].children[0]; + let color = scalePath.getAttribute("fill"); + let sizeX = szX.value; + let sizeY = szY.value; + + if (color != accentColor || sizeX < 1 || sizeY < 1){ + //image will not be rezised Set desitred size to original size + sizeX = image.width; + sizeY = image.height; + //failsafe for not generating huge images automatically + if (image.width > 512 || image.height > 512) + { + sizeX = 16; + sizeY = 16; + } + } + + // Set the canvas size to the same as the desired image size + canvas.width = sizeX; + canvas.height = sizeY; + + imageInfo = '

Width: ' + sizeX + ', Height: ' + sizeY + ' (make sure this matches your led matrix setup)

' + + // Draw the image onto the canvas + context.drawImage(image, 0, 0, sizeX, sizeY); + + // Get the pixel data from the canvas + var pixelData = context.getImageData(0, 0, sizeX, sizeY).data; + + // Create an array to hold the RGB values of each pixel + var pixelRGBValues = []; + + // If the first row of the led matrix is right -> left + let right2leftAdjust = 1; + + if (ledSetupSelection == 'l2r'){ + right2leftAdjust = 0; + } + + // Loop through the pixel data and get the RGB values of each pixel + for (var i = 0; i < pixelData.length; i += 4) { + var r = pixelData[i]; + var g = pixelData[i + 1]; + var b = pixelData[i + 2]; + var a = pixelData[i + 3]; + + let pixel = i/4 + let row = Math.floor(pixel/sizeX); + let led = pixel; + if (ledSetupSelection == 'matrix'){ + //Do nothing, the matrix is set upp like the index in the image + //Every row starts from the left, i.e. no zigzagging + } + else if ((row + right2leftAdjust) % 2 === 0) { + //Setup is traditional zigzag + //right2leftAdjust basically flips the row order if = 1 + //Row is left to right + //Leave led index as pixel index + + } else { + //Setup is traditional zigzag + //Row is right to left + //Invert index of row for led + let indexOnRow = led - (row * sizeX); + let maxIndexOnRow = sizeX - 1; + let reversedIndexOnRow = maxIndexOnRow - indexOnRow; + led = (row * sizeX) + reversedIndexOnRow; + } + + // Add the RGB values to the pixel RGB values array + pixelRGBValues.push([r, g, b, a, led, pixel, row]); + } + + pixelRGBValues.sort((a, b) => a[5] - b[5]); + + //Copy the values to a new array for resorting + let ledRGBValues = [... pixelRGBValues]; + + //Sort the array based on led index + ledRGBValues.sort((a, b) => a[4] - b[4]); + + //Generate JSON in WLED format + let JSONledString = ''; + + //Set starting values for the segment check to something that is no color + let segmentStart = -1; + let maxi = ledRGBValues.length; + let curentColorIndex = 0 + let commandArray = []; + + //For evry pixel in the LED array + for (let i = 0; i < maxi; i++) { + let pixel = ledRGBValues[i]; + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = pixel[3]; + let segmentString = ''; + let segmentEnd = -1; + + if(segmentValueCheck){ + if (segmentStart < 0){ + //This is the first led of a new segment + segmentStart = i; + } //Else we allready have a start index + + if (i < maxi - 1){ + + let iNext = i + 1; + let nextPixel = ledRGBValues[iNext]; + + if (nextPixel[0] != r || nextPixel[1] != g || nextPixel[2] != b ){ + //Next pixel has new color + //The current segment ends with this pixel + segmentEnd = i + 1 //WLED wants the NEXT LED as the stop led... + if (segmentStart == i && hybridAddressing){ + //If only one led/pixel, no segment info needed + if (JSONledString == ''){ + //If addressing is single, we need to start every command with a starting possition + segmentString = '' + i + ','; + //Fixed to b2 + } else{ + segmentString = '' + } + } + else { + segmentString = segmentStart + ',' + segmentEnd + ','; + } + } + + } else { + //This is the last pixel, so the segment must end + segmentEnd = i + 1; + + if (segmentStart + 1 == segmentEnd && hybridAddressing){ + //If only one led/pixel, no segment info needed + if (JSONledString == ''){ + //If addressing is single, we need to start every command with a starting possition + segmentString = '' + i + ','; + //Fixed to b2 + } else{ + segmentString = '' + } + } + else { + segmentString = segmentStart + ',' + segmentEnd + ','; + } + } + } else{ + //Write every pixel + if (JSONledString == ''){ + //If addressing is single, we need to start every command with a starting possition + JSONledString = i + //Fixed to b2 + } + + segmentStart = i + segmentEnd = i + //Segment string should be empty for when addressing single. So no need to set it again. + } + + if (a < 255){ + hasTransparency = true; //If ANY pixel has alpha < 255 then this is set to true to warn the user + } + + if (segmentEnd > -1){ + //This is the last pixel in the segment, write to the JSONledString + //Return color value in selected format + let colorValueString = r + ',' + g + ',' + b ; + + if (hexValueCheck){ + const [red, green, blue] = [r, g, b]; + colorValueString = `${[red, green, blue].map(x => x.toString(16).padStart(2, '0')).join('')}`; + } else{ + //do nothing, allready set + } + + // Check if start and end is the same, in which case remove + + JSONledString += segmentString + colorSeparatorStart + colorValueString + colorSeparatorEnd; + fileJSON = JSONledString + segmentString + colorSeparatorStart + colorValueString + colorSeparatorEnd; + + curentColorIndex = curentColorIndex + 1; // We've just added a new color to the string so up the count with one + + if (curentColorIndex % maxNoOfColorsInCommandSting === 0 || i == maxi - 1) { + + //If we have accumulated the max number of colors to send in a single command or if this is the last pixel, we should write the current colorstring to the array + commandArray.push(JSONledString); + JSONledString = ''; //Start on an new command string + } else + { + //Add a comma to continue the command string + JSONledString = JSONledString + ',' + } + //Reset segment values + segmentStart = - 1; + } + } + + JSONledString = '' + + //For every commandString in the array + for (let i = 0; i < commandArray.length; i++) { + let thisJSONledString = `{"on":true,"bri":${brgh.value},"seg":{"id":${segID},"i":[${commandArray[i]}]}}`; + httpArray.push(thisJSONledString); + + let thiscurlString = `curl -X POST "http://${gurl.value}/json/state" -d \'${thisJSONledString}\' -H "Content-Type: application/json"`; + + //Aggregated Strings That should be returned to the user + if (i > 0){ + JSONledString = JSONledString + '\n'; + curlString = curlString + ' && '; + } + JSONledString += thisJSONledString; + curlString += thiscurlString; + } + + + haString = `#Uncomment if you don\'t allready have these defined in your switch section of your configuration.yaml +#- platform: command_line + #switches: + ${haIDe.value} + friendly_name: ${haNe.value} + unique_id: ${haUe.value} + command_on: > + ${curlString} + command_off: > + curl -X POST "http://${gurl.value}/json/state" -d \'{"on":false}\' -H "Content-Type: application/json"`; + + if (formatSelection == 'wled'){ + JLD.value = JSONledString; + } else if (formatSelection == 'curl'){ + JLD.value = curlString; + } else if (formatSelection == 'ha'){ + JLD.value = haString; + } else { + JLD.value = 'ERROR!/n' + formatSelection + ' is an unknown format.' + } + + fileJSON += ']}}'; + + let infoDiv = imin; + let canvasDiv = imin; + if (hasTransparency){ + imageInfo = imageInfo + '

WARNING! Transparency info detected in image. Transparency (alpha) has been ignored. To ensure you get the result you desire, use only solid colors in your image.

' + } + + infoDiv.innerHTML = imageInfo; + canvasDiv.style.display = "block" + + + //Drawing the image + drawBoxes(pixelRGBValues, sizeX, sizeY); + } +} \ No newline at end of file diff --git a/wled00/data/pixart/pixart.css b/wled00/data/pixart/pixart.css new file mode 100644 index 00000000..39ba1f28 --- /dev/null +++ b/wled00/data/pixart/pixart.css @@ -0,0 +1,324 @@ + +.box { + border: 2px solid #fff; +} +body { + font-family: Arial, sans-serif; + background-color: #111; +} + +.top-part { + width: 600px; + margin: 0 auto; +} +.container { + max-width: 100% -40px; + border-radius: 0px; + padding: 20px; + text-align: center; +} +h1 { + font-size: 2.3em; + color: #ddd; + margin: 1px 0; + font-family: Arial, sans-serif; + line-height: 0.5; + /*text-align: center;*/ +} +h2 { + font-size: 1.1em; + color: rgba(221, 221, 221, 0.61); + margin: 1px 0; + font-family: Arial, sans-serif; + line-height: 0.5; + text-align: center; +} +h3 { + font-size: 0.7em; + color: rgba(221, 221, 221, 0.61); + margin: 1px 0; + font-family: Arial, sans-serif; + line-height: 1.4; + text-align: center; + align-items: center; + justify-content: center; + display: flex; +} + +p { + font-size: 1em; + color: #777; + line-height: 1.5; + font-family: Arial, sans-serif; +} + +#fieldTable { + font-size: 1 em; + color: #777; + line-height: 1; + font-family: Arial, sans-serif; +} + +#scaleTable { + font-size: 1 em; + color: #777; + line-height: 1; + font-family: Arial, sans-serif; +} + +#drop-zone { + display: block; + width: 100%-40px; + border: 3px dashed #ddd; + border-radius: 0px; + text-align: center; + padding: 20px; + margin: 0px; + cursor: pointer; + font-family: Arial, sans-serif; + font-size: 15px; + color: #777; +} + +#file-picker { + display: none; +} +.adaptiveTD{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + +} + +.mainSelector { + background-color: #222; + color: #ddd; + border: 1px solid #333; + margin-top: 4px; + margin-bottom: 4px; + padding: 0 8px; + height: 28px; + font-size: 15px; + border-radius: 7px; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.adaptiveSelector { + background-color: #222; + color: #ddd; + border: 1px solid #333; + margin-top: 4px; + margin-bottom: 4px; + padding: 0 8px; + height: 28px; + font-size: 15px; + border-radius: 7px; + flex-grow: 1; + display: none; +} + +.segmentsDiv{ + width: 36px; + padding-left: 5px; +} + +* input[type=range] { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + flex-grow: 1; + padding: 0; + margin: 4px 8px 4px 0; + background-color: transparent; + cursor: pointer; + background: linear-gradient(to right, #bbb 50%, #333 50%); + border-radius: 7px; +} + +input[type=range]:focus { + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + height: 28px; + cursor: pointer; + background: transparent; + border-radius: 7px; +} +input[type=range]::-webkit-slider-thumb { + height: 16px; + width: 16px; + border-radius: 50%; + background: #fff; + cursor: pointer; + -webkit-appearance: none; + margin-top: 4px; + border-radius: 7px; +} +input[type=range]::-moz-range-track { + height: 28px; + background-color: rgba(0, 0, 0, 0); + border-radius: 7px; +} +input[type=range]::-moz-range-thumb { + border: 0px solid rgba(0, 0, 0, 0); + height: 16px; + width: 16px; + border-radius: 7px; + background: #fff; +} + +.rangeNumber{ + width: 20px; + vertical-align: middle; +} + +.fullTextField[type=text] { + background-color: #222; + border: 1px solid #333; + padding-inline-start: 5px; + margin-top: 4px; + margin-bottom: 4px; + height: 24px; + border-radius: 0px; + font-family: Arial, sans-serif; + font-size: 15px; + color: #ddd; + border-radius: 7px; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; +} +.flxTFld{ + background-color: #222; + border: 1px solid #333; + padding-inline-start: 5px; + height: 24px; + border-radius: 0px; + font-family: Arial, sans-serif; + font-size: 15px; + color: #ddd; + border-radius: 7px; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; +} + +* input[type=submit] { + background-color: #222; + border: 1px solid #333; + padding: 0.5em; + width: 100%; + border-radius: 24px; + font-family: Arial, sans-serif; + font-size: 1.3em; + color: #ddd; +} + +* button { + background-color: #222; + border: 1px solid #333; + padding-inline: 5px; + width: 100%; + border-radius: 24px; + font-family: Arial, sans-serif; + font-size: 1em; + color: #ddd; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +#scaleDiv { + display: flex; + align-items: center; + vertical-align: middle; +} + +textarea { + grid-row: 1 / 2; + width: 100%; + height: 200px; + background-color: #222; + border: 1px solid #333; + color: #ddd; +} +.hide { + display: none; +} + +.svg-icon { + vertical-align: middle; +} +#image-container { + display: grid; + grid-template-rows: 1fr 1fr; +} +#button-container { + display: flex; + padding-bottom: 10px; + padding-top: 10px; +} + +.buttonclass { + flex: 1; + padding-top: 5px; + padding-bottom: 5px; +} + +.gap { + width: 10px; +} + +#submitConvert::before { + content: ""; + display: inline-block; + background-image: url('data:image/svg+xml;utf8, '); + width: 36px; + height: 36px; +} + +#sizeDiv * { + display: inline-block; +} +.sizeInputFields{ + width: 50px; + background-color: #222; + border: 1px solid #333; + padding-inline-start: 5px; + margin-top: -5px; + height: 24px; + border-radius: 7px; + font-family: Arial, sans-serif; + font-size: 15px; + color: #ddd; +} +a:link { + color: rgba(221, 221, 221, 0.61); + background-color: transparent; + text-decoration: none; +} + +a:visited { + color: rgba(221, 221, 221, 0.61); + background-color: transparent; + text-decoration: none; +} + +a:hover { + color: #ddd; + background-color: transparent; + text-decoration: none; +} + +a:active { + color: rgba(221, 221, 221, 0.61); + background-color: transparent; + text-decoration: none; +} \ No newline at end of file diff --git a/wled00/data/pixart/pixart.htm b/wled00/data/pixart/pixart.htm new file mode 100644 index 00000000..889bd37d --- /dev/null +++ b/wled00/data/pixart/pixart.htm @@ -0,0 +1,210 @@ + + + + + + + WLED Pixel Art Converter + + + + + + +
+
+

+ + + + + + + WLED Pixel Art Converter +

+
+

Convert image to WLED JSON (pixel art on WLED matrix)

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + 128 +
+ + + + 256 +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + +
+
+ + + + + +
+
+ + + +  Scale image +
+
+ +
+

+ +

+

+ Drop image here
or
+ Click to select a file +
+ +

+ +

+ +

+ +
+ + + +

+ + + + +
+

Version 1.0.7
 -  Help/About

+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/wled00/data/pixart/pixart.js b/wled00/data/pixart/pixart.js new file mode 100644 index 00000000..a1c67aa9 --- /dev/null +++ b/wled00/data/pixart/pixart.js @@ -0,0 +1,364 @@ +//Start up code +//if (window.location.protocol == "file:") { +// let locip = prompt("File Mode. Please enter WLED IP!"); +// gId('curlUrl').value = locip; +//} else +// +//Start up code +let devMode = false; //Remove +gurl.value = location.host; + +const urlParams = new URLSearchParams(window.location.search); +if (gurl.value.length < 1){ + gurl.value = "Missing_Host"; +} + +function gen(){ + //Generate image if enough info is in place + //Is host non empty + //Is image loaded + //is scale > 0 + if (((szX.value > 0 && szY.value > 0) || szDiv.style.display == 'none') && gurl.value.length > 0 && prw.style.display != 'none'){ + //regenerate + let base64Image = prw.src; + if (isValidBase64Gif(base64Image)) { + im.src = base64Image; + getPixelRGBValues(base64Image); + imcn.style.display = "block"; + bcn.style.display = ""; + } else { + let imageInfo = '

WARNING! File does not appear to be a valid image

'; + imin.innerHTML = imageInfo; + imin.style.display = "block"; + imcn.style.display = "none"; + JLD.value = ''; + if (devMode) console.log("The string '" + base64Image + "' is not a valid base64 image."); + } + } + + if(gurl.value.length > 0){ + gId("sSg").setAttribute("fill", accentColor); + } else{ + gId("sSg").setAttribute("fill", accentTextColor); + let ts = tSg; + ts.style.display = "none"; + ts.innerHTML = ""; + sID.style.display = "flex"; + } +} + + +// Code for copying the generated string to clipboard + +cjb.addEventListener('click', async () => { + let JSONled = JLD; + JSONled.select(); + try { + await navigator.clipboard.writeText(JSONled.value); + } catch (err) { + try { + await d.execCommand("copy"); + } catch (err) { + console.error('Failed to copy text: ', err); + } + } +}); + +// Event listeners ======================= + +lSS.addEventListener("change", gen); +szY.addEventListener("change", gen); +szX.addEventListener("change", gen); +//frm.addEventListener("change", gen); +cFS.addEventListener("change", gen); +aS.addEventListener("change", gen); +brgh.addEventListener("change", gen); +cLN.addEventListener("change", gen); +haIDe.addEventListener("change", gen); +haUe.addEventListener("change", gen); +haNe.addEventListener("change", gen); +gurl.addEventListener("change", gen); +sID.addEventListener("change", gen); +prw.addEventListener("load", gen); +//gId("convertbutton").addEventListener("click", gen); + +tSg.addEventListener("change", () => { + sop = tSg.options[tSg.selectedIndex]; + szX.value = sop.dataset.x; + szY.value = sop.dataset.y; + gen(); +}); + +gId("sendJSONledbutton").addEventListener('click', async () => { + if (window.location.protocol === "https:") { + alert('Will only be available when served over http (or WLED is run over https)'); + } else { + postPixels(); + } +}); + +brgh.oninput = () => { + brgV.textContent = brgh.value; + let perc = parseInt(brgh.value)*100/255; + var val = `linear-gradient(90deg, #bbb ${perc}%, #333 ${perc}%)`; + brgh.style.backgroundImage = val; +} + +cLN.oninput = () => { + let cln = cLN; + cLV.textContent = cln.value; + let perc = parseInt(cln.value)*100/512; + var val = `linear-gradient(90deg, #bbb ${perc}%, #333 ${perc}%)`; + cln.style.backgroundImage = val; +} + +frm.addEventListener("change", () => { + for (var i = 0; i < hideableRows.length; i++) { + hideableRows[i].classList.toggle("hide", frm.value !== "ha"); + } +}); + +async function postPixels() { + let ss = gId("sendSvgP"); + ss.setAttribute("fill", prsCol); + let er = false; + for (let i of httpArray) { + try { + if (devMode) console.log(i); + if (devMode) console.log(i.length); + const response = await fetch('http://'+gId('curlUrl').value+'/json/state', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + //'Content-Type': 'text/html; charset=UTF-8' + }, + body: i + }); + const data = await response.json(); + if (devMode) console.log(data); + } catch (error) { + console.error(error); + er = true; + } + } + if(er){ + //Something went wrong + ss.setAttribute("fill", redColor); + setTimeout(function(){ + ss.setAttribute("fill", accentTextColor); + }, 1000); + } else { + // A, OK + ss.setAttribute("fill", greenColor); + setTimeout(function(){ + ss.setAttribute("fill", accentColor); + }, 1000); + } +} + +//File uploader code +const dropZone = gId('drop-zone'); +const filePicker = gId('file-picker'); +const preview = prw; + +// Listen for dragenter, dragover, and drop events +dropZone.addEventListener('dragenter', dragEnter); +dropZone.addEventListener('dragover', dragOver); +dropZone.addEventListener('drop', dropped); +dropZone.addEventListener('click', zoneClicked); + +// Listen for change event on file picker +filePicker.addEventListener('change', filePicked); + +// Handle zone click +function zoneClicked(e) { + e.preventDefault(); + //this.classList.add('drag-over'); + //alert('Hej'); + filePicker.click(); +} + +// Handle dragenter +function dragEnter(e) { + e.preventDefault(); + this.classList.add('drag-over'); +} + +// Handle dragover +function dragOver(e) { + e.preventDefault(); +} + +// Handle drop +function dropped(e) { + e.preventDefault(); + this.classList.remove('drag-over'); + + // Get the dropped file + const file = e.dataTransfer.files[0]; + updatePreview(file) +} + +// Handle file picked +function filePicked(e) { + // Get the picked file + const file = e.target.files[0]; + updatePreview(file) +} + +// Update the preview image +function updatePreview(file) { + // Use FileReader to read the file + const reader = new FileReader(); + reader.onload = () => { + // Update the preview image + preview.src = reader.result; + //gId("submitConvertDiv").style.display = ""; + prw.style.display = ""; + }; + reader.readAsDataURL(file); +} + +function isValidBase64Gif(string) { + // Use a regular expression to check that the string is a valid base64 string + /* + const base64gifPattern = /^data:image\/gif;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/; + const base64pngPattern = /^data:image\/png;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/; + const base64jpgPattern = /^data:image\/jpg;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/; + const base64webpPattern = /^data:image\/webp;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/; + */ + //REMOVED, Any image appear to work as long as it can be drawn to the canvas. Leaving code in for future use, possibly + if (1==1 || base64gifPattern.test(string) || base64pngPattern.test(string) || base64jpgPattern.test(string) || base64webpPattern.test(string)) { + return true; + } else { + //Not OK + return false; + } +} + +var hideableRows = d.querySelectorAll(".ha-hide"); +for (var i = 0; i < hideableRows.length; i++) { + hideableRows[i].classList.add("hide"); +} +frm.addEventListener("change", () => { + for (var i = 0; i < hideableRows.length; i++) { + hideableRows[i].classList.toggle("hide", frm.value !== "ha"); + } +}); + +function switchScale() { + //let scalePath = gId("scaleDiv").children[1].children[0] + let scaleTogglePath = scDiv.children[0].children[0] + let color = scaleTogglePath.getAttribute("fill"); + let d = ''; + if (color === accentColor) { + color = accentTextColor; + d = scaleToggleOffd; + szDiv.style.display = "none"; + // Set values to actual XY of image, if possible + } else { + color = accentColor; + d = scaleToggleOnd; + szDiv.style.display = ""; + } + //scalePath.setAttribute("fill", color); + scaleTogglePath.setAttribute("fill", color); + scaleTogglePath.setAttribute("d", d); + gen(); +} + +function generateSegmentOptions(array) { + //This function is prepared for a name property on each segment for easier selection + //Currently the name is generated generically based on index + tSg.innerHTML = ""; + for (var i = 0; i < array.length; i++) { + var option = cE("option"); + option.value = array[i].value; + option.text = array[i].text; + option.dataset.x = array[i].x; + option.dataset.y = array[i].y; + tSg.appendChild(option); + if(i === 0) { + option.selected = true; + szX.value = option.dataset.x; + szY.value = option.dataset.y; + } + } +} + +// Get segments from device +async function getSegments() { + cv = gurl.value; + if (cv.length > 0 ){ + try { + var arr = []; + const response = await fetch('http://'+cv+'/json/state'); + const json = await response.json(); + let ids = json.seg.map(sg => ({id: sg.id, n: sg.n, xs: sg.start, xe: sg.stop, ys: sg.startY, ye: sg.stopY})); + for (var i = 0; i < ids.length; i++) { + arr.push({ + value: ids[i]["id"], + text: ids[i]["n"] + ' (index: ' + ids[i]["id"] + ')', + x: ids[i]["xe"] - ids[i]["xs"], + y: ids[i]["ye"] - ids[i]["ys"] + }); + } + generateSegmentOptions(arr); + tSg.style.display = "flex"; + sID.style.display = "none"; + gId("sSg").setAttribute("fill", greenColor); + setTimeout(function(){ + gId("sSg").setAttribute("fill", accentColor); + }, 1000); + + } catch (error) { + console.error(error); + gId("sSg").setAttribute("fill", redColor); + setTimeout(function(){ + gId("sSg").setAttribute("fill", accentColor); + }, 1000); + tSg.style.display = "none"; + sID.style.display = "flex"; + } + } else{ + gId("sSg").setAttribute("fill", redColor); + setTimeout(function(){ + gId("sSg").setAttribute("fill", accentTextColor); + }, 1000); + tSg.style.display = "none"; + sID.style.display = "flex"; + } +} + +//Initial population of segment selection +function generateSegmentArray(noOfSegments) { + var arr = []; + for (var i = 0; i < noOfSegments; i++) { + arr.push({ + value: i, + text: "Segment index " + i + }); + } + return arr; +} + +var segmentData = generateSegmentArray(10); + +generateSegmentOptions(segmentData); + +seDiv.innerHTML = +'' +/*gId("convertbutton").innerHTML = +'   Convert to WLED JSON '; +*/ +cjb.innerHTML = +'   Copy to clipboard'; +gId("sendJSONledbutton").innerHTML = +'   Send to device'; + +//After everything is loaded, check if we have a possible IP/host + +if(gurl.value.length > 0){ + // Needs to be addressed directly here so the object actually exists + gId("sSg").setAttribute("fill", accentColor); +} diff --git a/wled00/data/pixart/site.webmanifest b/wled00/data/pixart/site.webmanifest new file mode 100644 index 00000000..82452af2 --- /dev/null +++ b/wled00/data/pixart/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "WLED Pixel Art Convertor", + "short_name": "ledconv", + "icons": [ + { + "src": "/favicon-32x32.png", + "sizes": "32x322", + "type": "image/png" + }, + { + "src": "/favicon-32x32.png", + "sizes": "32x32", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" + } \ No newline at end of file diff --git a/wled00/data/pixart/statics.js b/wled00/data/pixart/statics.js new file mode 100644 index 00000000..b4f3c407 --- /dev/null +++ b/wled00/data/pixart/statics.js @@ -0,0 +1,51 @@ +//elements +var gurl = gId('curlUrl'); +var szX = gId("sizeX"); +var szY = gId("sizeY"); +var szDiv = gId("sizeDiv"); +var prw = gId("preview"); +var sID = gId('segID'); +var JLD = gId('JSONled'); +var tSg = gId('targetSegment'); +var brgh = gId("brightnessNumber"); + +var seDiv = gId("getSegmentsDiv") +var cjb = gId("copyJSONledbutton"); +var frm = gId("formatSelector"); +var cLN = gId("colorLimitNumber"); +var haIDe = gId("haID"); +var haUe = gId("haUID"); +var haNe = gId("haName"); +var aS = gId("addressingSelector"); +var cFS = gId("colorFormatSelector"); +var lSS = gId("ledSetupSelector"); +var imin = gId('image-info'); +var imcn = gId('image-container'); +var bcn = gId("button-container"); +var im = gId('image'); +//var ss = gId("sendSvgP"); +var scDiv = gId("scaleDiv"); +var w = window; +var canvas = gId('pixelCanvas'); +var brgV = gId("brightnessValue"); +var cLV = gId("colorLimitValue") + +//vars +var httpArray = []; +var fileJSON = ''; + +var hideableRows = d.querySelectorAll(".ha-hide"); +for (var i = 0; i < hideableRows.length; i++) { + hideableRows[i].classList.add("hide"); +} + +var accentColor = '#eee'; +var accentTextColor = '#777'; +var prsCol = '#ccc'; +var greenColor = '#056b0a'; +var redColor = '#6b050c'; + +var scaleToggleOffd = "M17,7H7A5,5 0 0,0 2,12A5,5 0 0,0 7,17H17A5,5 0 0,0 22,12A5,5 0 0,0 17,7M7,15A3,3 0 0,1 4,12A3,3 0 0,1 7,9A3,3 0 0,1 10,12A3,3 0 0,1 7,15Z"; +var scaleToggleOnd = "M17,7H7A5,5 0 0,0 2,12A5,5 0 0,0 7,17H17A5,5 0 0,0 22,12A5,5 0 0,0 17,7M17,15A3,3 0 0,1 14,12A3,3 0 0,1 17,9A3,3 0 0,1 20,12A3,3 0 0,1 17,15Z"; + +var sSg = gId("getSegmentsSVGpath"); \ No newline at end of file diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 20b8de8d..45eb4980 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -9,6 +9,10 @@ var d=document,laprev=55,maxB=1,maxV=0,maxM=4000,maxPB=4096,maxL=1333,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var customStarts=false,startsDirty=[],maxCOOverrides=5; var loc = false, locip; + d.um_p = []; + d.rsvd = []; + d.ro_gpio = []; + d.max_gpio = 39; function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");} function B(){window.open("/settings","_self");} function gId(n){return d.getElementById(n);} @@ -23,10 +27,6 @@ // success event scE.addEventListener("load", () => { //console.log("File loaded"); - d.um_p = []; - d.rsvd = []; - d.ro_pins = []; - d.max_gpio = 39; GetV();checkSi();setABL(); if (d.um_p[0]==-1) d.um_p.shift(); }); @@ -66,7 +66,7 @@ for (k=0;ke==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;} - else if (!(nm == "IR" || nm=="BT") && d.ro_pins.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);LCs[i].value="";LCs[i].focus();return false;} + else if (!(nm == "IR" || nm=="BT") && d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);LCs[i].value="";LCs[i].focus();return false;} for (j=i+1; jbeginResponse_P(200, "text/html", PAGE_pixart, PAGE_pixart_L); + response->addHeader(FPSTR(s_content_enc),"gzip"); + setStaticContentCacheHeaders(response); + request->send(response); + }); + #endif + #ifdef WLED_ENABLE_WEBSOCKETS server.addHandler(&ws); #endif