| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | #include "PanelInterleaveHandler.h" | ||
| 13 | |||
| 14 | #include <cstdint> | ||
| 15 | #include <vector> | ||
| 16 | |||
| 17 | 15 | PanelInterleaveHandler::PanelInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 18 | 15 | m_panelWidth(pw), m_panelHeight(ph), m_panelScan(ps), m_interleave(pi) { | |
| 19 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | m_map.resize(m_panelWidth * m_panelHeight); |
| 20 | 15 | } | |
| 21 | |||
| 22 | 15 | bool PanelInterleaveHandler::mapFitsFrame() const { | |
| 23 | // Callers address their frame buffers as [bit][mapped y][chain * xSpan][gpio], | ||
| 24 | // where the buffer holds exactly m_panelScan rows and each panel occupies | ||
| 25 | // xSpan = m_panelWidth * m_panelHeight / (m_panelScan * 2) columns; the | ||
| 26 | // extra columns are where a scan rate below half the panel height folds | ||
| 27 | // its rows. Only y in [0, m_panelHeight / 2) is ever mapped - the two | ||
| 28 | // halves of the panel are driven together - so that is the range checked. | ||
| 29 | // A map that leaves the frame does not merely look wrong on the panel, it | ||
| 30 | // writes past the end of the allocation, so it has to be refused here | ||
| 31 | // rather than trusted by every caller in turn. | ||
| 32 |
3/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
|
15 | if (m_panelScan <= 0 || m_panelWidth <= 0 || m_panelHeight <= 0) { |
| 33 | ✗ | return false; | |
| 34 | } | ||
| 35 | 15 | const int xSpan = m_panelWidth * m_panelHeight / (m_panelScan * 2); | |
| 36 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | if (xSpan <= 0) { |
| 37 | ✗ | return false; | |
| 38 | } | ||
| 39 |
2/2✓ Branch 0 taken 244 times.
✓ Branch 1 taken 15 times.
|
259 | for (int y = 0; y < (m_panelHeight / 2); ++y) { |
| 40 |
2/2✓ Branch 0 taken 15936 times.
✓ Branch 1 taken 244 times.
|
16180 | for (int x = 0; x < m_panelWidth; ++x) { |
| 41 | // the map holds uint16_t, so a negative mapping has already | ||
| 42 | // wrapped to a large value and trips these same checks | ||
| 43 | 15936 | const auto& p = m_map[y * m_panelWidth + x]; | |
| 44 |
2/4✓ Branch 0 taken 15936 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15936 times.
|
15936 | if (p.first >= xSpan || p.second >= m_panelScan) { |
| 45 | ✗ | return false; | |
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | 15 | return true; | |
| 50 | } | ||
| 51 | |||
| 52 | class NoInterleaveHandler : public PanelInterleaveHandler { | ||
| 53 | public: | ||
| 54 | 15 | NoInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 55 | 15 | PanelInterleaveHandler(pw, ph, ps, pi) { | |
| 56 |
2/2✓ Branch 0 taken 488 times.
✓ Branch 1 taken 15 times.
|
503 | for (int y = 0; y < m_panelHeight; ++y) { |
| 57 |
2/2✓ Branch 0 taken 31872 times.
✓ Branch 1 taken 488 times.
|
32360 | for (int x = 0; x < m_panelWidth; ++x) { |
| 58 | 31872 | int idx = (y * m_panelWidth + x); | |
| 59 | 31872 | m_map[idx] = std::make_pair(x, y); | |
| 60 | } | ||
| 61 | } | ||
| 62 | 15 | } | |
| 63 | 60 | virtual ~NoInterleaveHandler() {} | |
| 64 | }; | ||
| 65 | |||
| 66 | /* | ||
| 67 | For simple interleave of 16: on 32 wide panel | ||
| 68 | 63----48 31----16 | ||
| 69 | |||
| 70 | 47----32 15----0 | ||
| 71 | */ | ||
| 72 | class SimpleInterleaveHandler : public PanelInterleaveHandler { | ||
| 73 | public: | ||
| 74 | ✗ | SimpleInterleaveHandler(int pw, int ph, int ps, int pi, bool flip) : | |
| 75 | PanelInterleaveHandler(pw, ph, ps, pi), | ||
| 76 | ✗ | m_flipRows(flip) { | |
| 77 | ✗ | for (int y = 0; y < m_panelHeight; ++y) { | |
| 78 | ✗ | int y2 = y; | |
| 79 | ✗ | mapRow(y2); | |
| 80 | ✗ | for (int x = 0; x < m_panelWidth; ++x) { | |
| 81 | ✗ | int x2 = x; | |
| 82 | ✗ | mapCol(y, x2); | |
| 83 | ✗ | int idx = (y * m_panelWidth + x); | |
| 84 | ✗ | m_map[idx] = std::make_pair(x2, y2); | |
| 85 | } | ||
| 86 | } | ||
| 87 | ✗ | } | |
| 88 | ✗ | virtual ~SimpleInterleaveHandler() {} | |
| 89 | |||
| 90 | ✗ | virtual void mapRow(int& y) { | |
| 91 | ✗ | while (y >= m_panelScan) { | |
| 92 | ✗ | y -= m_panelScan; | |
| 93 | } | ||
| 94 | ✗ | } | |
| 95 | ✗ | virtual void mapCol(int y, int& x) { | |
| 96 | ✗ | int whichInt = x / m_interleave; | |
| 97 | ✗ | if (m_flipRows) { | |
| 98 | // swap each pair of scan blocks (rows 0..scan-1 with scan..2*scan-1 | ||
| 99 | // and so on). For a power of two scan this is y ^ scan; the | ||
| 100 | // division also covers scans like 6. | ||
| 101 | ✗ | int block = y / m_panelScan; | |
| 102 | ✗ | y = (block ^ 1) * m_panelScan + y % m_panelScan; | |
| 103 | } | ||
| 104 | ✗ | int offInInt = x % m_interleave; | |
| 105 | ✗ | int mult = (m_panelHeight / m_panelScan / 2) - 1 - y / m_panelScan; | |
| 106 | ✗ | x = m_interleave * (whichInt * m_panelHeight / m_panelScan / 2 + mult) + offInInt; | |
| 107 | ✗ | } | |
| 108 | |||
| 109 | private: | ||
| 110 | const bool m_flipRows; | ||
| 111 | }; | ||
| 112 | |||
| 113 | /* | ||
| 114 | On 32 wide panel: | ||
| 115 | 63----48 15---0 | ||
| 116 | 47----32 31--16 | ||
| 117 | */ | ||
| 118 | class ZigZagInterleaveHandler : public PanelInterleaveHandler { | ||
| 119 | public: | ||
| 120 | ✗ | ZigZagInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 121 | ✗ | PanelInterleaveHandler(pw, ph, ps, pi) { | |
| 122 | ✗ | for (int y = 0; y < m_panelHeight; ++y) { | |
| 123 | ✗ | int y2 = y; | |
| 124 | ✗ | mapRow(y2); | |
| 125 | ✗ | for (int x = 0; x < m_panelWidth; ++x) { | |
| 126 | ✗ | int x2 = x; | |
| 127 | ✗ | mapCol(y, x2); | |
| 128 | ✗ | int idx = (y * m_panelWidth + x); | |
| 129 | ✗ | m_map[idx] = std::make_pair(x2, y2); | |
| 130 | } | ||
| 131 | } | ||
| 132 | ✗ | } | |
| 133 | ✗ | virtual ~ZigZagInterleaveHandler() {} | |
| 134 | |||
| 135 | ✗ | virtual void mapRow(int& y) { | |
| 136 | ✗ | while (y >= m_panelScan) { | |
| 137 | ✗ | y -= m_panelScan; | |
| 138 | } | ||
| 139 | ✗ | } | |
| 140 | ✗ | virtual void mapCol(int y, int& x) { | |
| 141 | ✗ | int whichInt = x / m_interleave; | |
| 142 | ✗ | int offInInt = x % m_interleave; | |
| 143 | ✗ | int mult = y / m_panelScan; | |
| 144 | |||
| 145 | ✗ | if (m_panelScan == 2) { | |
| 146 | ✗ | if ((y & 0x2) == 0) { | |
| 147 | ✗ | offInInt = m_interleave - 1 - offInInt; | |
| 148 | } | ||
| 149 | ✗ | } else if ((whichInt & 0x1) == 1) { | |
| 150 | ✗ | mult = (y < m_panelScan ? y + m_panelScan : y - m_panelScan) / m_panelScan; | |
| 151 | } | ||
| 152 | ✗ | x = m_interleave * (whichInt * m_panelHeight / m_panelScan / 2 + mult) + offInInt; | |
| 153 | ✗ | } | |
| 154 | }; | ||
| 155 | |||
| 156 | /* | ||
| 157 | For 32w panel: | ||
| 158 | 32----47 0----15 | ||
| 159 | 63----48 31----16 | ||
| 160 | */ | ||
| 161 | class ZigZagClusterInterleaveHandler : public PanelInterleaveHandler { | ||
| 162 | public: | ||
| 163 | ✗ | ZigZagClusterInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 164 | ✗ | PanelInterleaveHandler(pw, ph, ps, pi) { | |
| 165 | ✗ | for (int y = 0; y < m_panelHeight; ++y) { | |
| 166 | ✗ | int y2 = y; | |
| 167 | ✗ | mapRow(y2); | |
| 168 | ✗ | for (int x = 0; x < m_panelWidth; ++x) { | |
| 169 | ✗ | int x2 = x; | |
| 170 | ✗ | mapCol(y, x2); | |
| 171 | ✗ | int idx = (y * m_panelWidth + x); | |
| 172 | ✗ | m_map[idx] = std::make_pair(x2, y2); | |
| 173 | } | ||
| 174 | } | ||
| 175 | ✗ | } | |
| 176 | ✗ | virtual ~ZigZagClusterInterleaveHandler() {} | |
| 177 | |||
| 178 | ✗ | virtual void mapRow(int& y) { | |
| 179 | ✗ | while (y >= m_panelScan) { | |
| 180 | ✗ | y -= m_panelScan; | |
| 181 | } | ||
| 182 | ✗ | } | |
| 183 | ✗ | virtual void mapCol(int y, int& x) { | |
| 184 | ✗ | int whichInt = x / m_interleave; | |
| 185 | ✗ | int offInInt = x % m_interleave; | |
| 186 | ✗ | int mult = y / m_panelScan; | |
| 187 | |||
| 188 | ✗ | if (m_panelScan == 2) { | |
| 189 | // AC_MAPPING code - stealing codepath for scan 1/2 and zigzag8 | ||
| 190 | // cluster -- the ordinal number of a group of 8 linear lights on a half-frame | ||
| 191 | // starting in the top left corner of a half-module; bits 3,2 from y, 1,0 from x | ||
| 192 | ✗ | int tc = whichInt | (y << 1) & 0xc; | |
| 193 | // mapped cluster - this reverses the effects of unusual wiring on this panel | ||
| 194 | // address bits are shifted around and bit3 is negated to achieve linear counting | ||
| 195 | ✗ | uint8_t map_cb = (~tc & 8) | (tc & 4) >> 2 | (tc & 2) << 1 | (tc & 1) << 1; | |
| 196 | // scale up from cluster to pixel counts and account for reverse-running clusters | ||
| 197 | ✗ | x = map_cb * 8 + (x & 0x7 ^ (((~y >> 1) & 1) * 7)); | |
| 198 | ✗ | return; | |
| 199 | ✗ | } else if (m_interleave == 4) { | |
| 200 | ✗ | if ((whichInt & 0x1) == 1) { | |
| 201 | ✗ | mult = (y < m_panelScan ? y + m_panelScan : y - m_panelScan) / m_panelScan; | |
| 202 | } | ||
| 203 | } else { | ||
| 204 | ✗ | int tmp = (y * 2) / m_panelScan; | |
| 205 | ✗ | if ((tmp & 0x2) == 0) { | |
| 206 | ✗ | offInInt = m_interleave - 1 - offInInt; | |
| 207 | } | ||
| 208 | } | ||
| 209 | ✗ | x = m_interleave * (whichInt * m_panelHeight / m_panelScan / 2 + mult) + offInInt; | |
| 210 | } | ||
| 211 | }; | ||
| 212 | |||
| 213 | class StripeClusterInterleaveHandler : public PanelInterleaveHandler { | ||
| 214 | public: | ||
| 215 | static constexpr int MAPPING[8][4] = { | ||
| 216 | { 32, 48, 96, 112 }, | ||
| 217 | { 32, 48, 96, 112 }, | ||
| 218 | { 40, 56, 104, 120 }, | ||
| 219 | { 40, 56, 104, 120 }, | ||
| 220 | { 0, 16, 64, 80 }, | ||
| 221 | { 0, 16, 64, 80 }, | ||
| 222 | { 8, 24, 72, 88 }, | ||
| 223 | { 8, 24, 72, 88 } | ||
| 224 | }; | ||
| 225 | |||
| 226 | ✗ | StripeClusterInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 227 | ✗ | PanelInterleaveHandler(pw, ph, ps, pi) { | |
| 228 | ✗ | for (int y = 0; y < m_panelHeight; ++y) { | |
| 229 | ✗ | int y2 = y; | |
| 230 | ✗ | mapRow(y2); | |
| 231 | ✗ | for (int x = 0; x < m_panelWidth; ++x) { | |
| 232 | ✗ | int x2 = x; | |
| 233 | ✗ | mapCol(y, x2); | |
| 234 | ✗ | int idx = (y * m_panelWidth + x); | |
| 235 | ✗ | m_map[idx] = std::make_pair(x2, y2); | |
| 236 | } | ||
| 237 | } | ||
| 238 | ✗ | } | |
| 239 | ✗ | virtual ~StripeClusterInterleaveHandler() {} | |
| 240 | |||
| 241 | ✗ | virtual void mapRow(int& y) { | |
| 242 | ✗ | while (y >= m_panelScan) { | |
| 243 | ✗ | y -= m_panelScan; | |
| 244 | } | ||
| 245 | ✗ | } | |
| 246 | ✗ | virtual void mapCol(int y, int& x) { | |
| 247 | ✗ | int whichInt = x / m_interleave; | |
| 248 | ✗ | int offInInt = x % m_interleave; | |
| 249 | ✗ | x = MAPPING[y % 8][whichInt % 4] + offInInt; | |
| 250 | ✗ | } | |
| 251 | }; | ||
| 252 | |||
| 253 | class PixelMapper { | ||
| 254 | public: | ||
| 255 | ✗ | virtual ~PixelMapper() {} | |
| 256 | |||
| 257 | // Get the name of this PixelMapper. Each PixelMapper needs to have a name | ||
| 258 | // so that it can be referred to with command line flags. | ||
| 259 | virtual const char* GetName() const = 0; | ||
| 260 | |||
| 261 | // Pixel mappers receive the chain and parallel information and | ||
| 262 | // might receive optional user-parameters, e.g. from command line flags. | ||
| 263 | // | ||
| 264 | // This is a single string containing the parameters. | ||
| 265 | // You can be used from simple scalar parameters, such as the angle for | ||
| 266 | // the rotate transformer, or more complex parameters that describe a mapping | ||
| 267 | // of panels for instance. | ||
| 268 | // Keep it concise (as people will give parameters on the command line) and | ||
| 269 | // don't use semicolons in your string (as they are | ||
| 270 | // used to separate pixel mappers on the command line). | ||
| 271 | // | ||
| 272 | // For instance, the rotate transformer is invoked like this | ||
| 273 | // --led-pixel-mapper=rotate:90 | ||
| 274 | // And the parameter that is passed to SetParameter() is "90". | ||
| 275 | // | ||
| 276 | // Returns 'true' if parameter was parsed successfully. | ||
| 277 | ✗ | virtual bool SetParameters(int chain, int parallel, | |
| 278 | const char* parameter_string) { | ||
| 279 | ✗ | return true; | |
| 280 | } | ||
| 281 | |||
| 282 | // Given a underlying matrix (width, height), returns the | ||
| 283 | // visible (width, height) after the mapping. | ||
| 284 | // E.g. a 90 degree rotation might map matrix=(64, 32) -> visible=(32, 64) | ||
| 285 | // Some multiplexing matrices will double the height and half the width. | ||
| 286 | // | ||
| 287 | // While not technically necessary, one would expect that the number of | ||
| 288 | // pixels stay the same, so | ||
| 289 | // matrix_width * matrix_height == (*visible_width) * (*visible_height); | ||
| 290 | // | ||
| 291 | // Returns boolean "true" if the mapping can be successfully done with this | ||
| 292 | // mapper. | ||
| 293 | virtual bool GetSizeMapping(int matrix_width, int matrix_height, | ||
| 294 | int* visible_width, int* visible_height) | ||
| 295 | const = 0; | ||
| 296 | |||
| 297 | // Map where a visible pixel (x,y) is mapped to the underlying matrix (x,y). | ||
| 298 | // | ||
| 299 | // To be convienently stateless, the first parameters are the full | ||
| 300 | // matrix width and height. | ||
| 301 | // | ||
| 302 | // So for many multiplexing methods this means to map a panel to a double | ||
| 303 | // length and half height panel (32x16 -> 64x8). | ||
| 304 | // The logic_x, logic_y are output parameters and guaranteed not to be | ||
| 305 | // nullptr. | ||
| 306 | virtual void MapVisibleToMatrix(int matrix_width, int matrix_height, | ||
| 307 | int visible_x, int visible_y, | ||
| 308 | int* matrix_x, int* matrix_y) const = 0; | ||
| 309 | }; | ||
| 310 | class MultiplexMapper : public PixelMapper { | ||
| 311 | public: | ||
| 312 | // Function that edits the original rows and columns of the panels | ||
| 313 | // provided by the user to the actual underlying mapping. This is called | ||
| 314 | // before we do the actual set-up of the GPIO mapping as this influences | ||
| 315 | // the hardware interface. | ||
| 316 | // This is so that the user can provide the rows/columns they see. | ||
| 317 | virtual void EditColsRows(int* cols, int* rows) const = 0; | ||
| 318 | }; | ||
| 319 | |||
| 320 | class MultiplexMapperBase : public MultiplexMapper { | ||
| 321 | public: | ||
| 322 | ✗ | MultiplexMapperBase(const char* name, int stretch_factor) : | |
| 323 | ✗ | name_(name), panel_stretch_factor_(stretch_factor) {} | |
| 324 | |||
| 325 | // This method is const, but we sneakily remember the original size | ||
| 326 | // of the panels so that we can more easily quantize things. | ||
| 327 | // So technically, we're stateful, but let's pretend we're not changing | ||
| 328 | // state. In the context this is used, it is never accessed in multiple | ||
| 329 | // threads. | ||
| 330 | ✗ | virtual void EditColsRows(int* cols, int* rows) const { | |
| 331 | ✗ | panel_rows_ = *rows; | |
| 332 | ✗ | panel_cols_ = *cols; | |
| 333 | |||
| 334 | ✗ | *rows /= panel_stretch_factor_; | |
| 335 | ✗ | *cols *= panel_stretch_factor_; | |
| 336 | ✗ | } | |
| 337 | |||
| 338 | ✗ | virtual bool GetSizeMapping(int matrix_width, int matrix_height, | |
| 339 | int* visible_width, int* visible_height) const { | ||
| 340 | // Matrix width has been altered. Alter it back. | ||
| 341 | ✗ | *visible_width = matrix_width / panel_stretch_factor_; | |
| 342 | ✗ | *visible_height = matrix_height * panel_stretch_factor_; | |
| 343 | ✗ | return true; | |
| 344 | } | ||
| 345 | |||
| 346 | ✗ | virtual const char* GetName() const { return name_; } | |
| 347 | |||
| 348 | // The MapVisibleToMatrix() as required by PanelMatrix here breaks it | ||
| 349 | // down to the individual panel, so that derived classes only need to | ||
| 350 | // implement MapSinglePanel(). | ||
| 351 | ✗ | virtual void MapVisibleToMatrix(int matrix_width, int matrix_height, | |
| 352 | int visible_x, int visible_y, | ||
| 353 | int* matrix_x, int* matrix_y) const { | ||
| 354 | ✗ | const int chained_panel = visible_x / panel_cols_; | |
| 355 | ✗ | const int parallel_panel = visible_y / panel_rows_; | |
| 356 | |||
| 357 | ✗ | const int within_panel_x = visible_x % panel_cols_; | |
| 358 | ✗ | const int within_panel_y = visible_y % panel_rows_; | |
| 359 | |||
| 360 | int new_x, new_y; | ||
| 361 | ✗ | MapSinglePanel(within_panel_x, within_panel_y, &new_x, &new_y); | |
| 362 | ✗ | *matrix_x = chained_panel * panel_stretch_factor_ * panel_cols_ + new_x; | |
| 363 | ✗ | *matrix_y = parallel_panel * panel_rows_ / panel_stretch_factor_ + new_y; | |
| 364 | ✗ | } | |
| 365 | |||
| 366 | // Map the coordinates for a single panel. This is to be overridden in | ||
| 367 | // derived classes. | ||
| 368 | // Input parameter is the visible position on the matrix, and this method | ||
| 369 | // should return the internal multiplexed position. | ||
| 370 | virtual void MapSinglePanel(int visible_x, int visible_y, | ||
| 371 | int* matrix_x, int* matrix_y) const = 0; | ||
| 372 | |||
| 373 | protected: | ||
| 374 | const char* const name_; | ||
| 375 | const int panel_stretch_factor_; | ||
| 376 | |||
| 377 | mutable int panel_cols_; | ||
| 378 | mutable int panel_rows_; | ||
| 379 | }; | ||
| 380 | |||
| 381 | /* ======================================================================== | ||
| 382 | * Multiplexer implementations. | ||
| 383 | * | ||
| 384 | * Extend MultiplexMapperBase and implement MapSinglePanel. You only have | ||
| 385 | * to worry about the mapping within a single panel, the overall panel | ||
| 386 | * construction with chains and parallel is already taken care of. | ||
| 387 | * | ||
| 388 | * Don't forget to register the new multiplexer sin CreateMultiplexMapperList() | ||
| 389 | * below. After that, the new mapper is available in the --led-multiplexing | ||
| 390 | * option. | ||
| 391 | */ | ||
| 392 | class StripeMultiplexMapper : public MultiplexMapperBase { | ||
| 393 | public: | ||
| 394 | ✗ | StripeMultiplexMapper() : | |
| 395 | ✗ | MultiplexMapperBase("Stripe", 2) {} | |
| 396 | |||
| 397 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 398 | ✗ | const bool is_top_stripe = (y % (panel_rows_ / 2)) < panel_rows_ / 4; | |
| 399 | ✗ | *matrix_x = is_top_stripe ? x + panel_cols_ : x; | |
| 400 | ✗ | *matrix_y = ((y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4)); | |
| 401 | ✗ | } | |
| 402 | }; | ||
| 403 | |||
| 404 | class FlippedStripeMultiplexMapper : public MultiplexMapperBase { | ||
| 405 | public: | ||
| 406 | ✗ | FlippedStripeMultiplexMapper() : | |
| 407 | ✗ | MultiplexMapperBase("FlippedStripe", 2) {} | |
| 408 | |||
| 409 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 410 | ✗ | const bool is_top_stripe = (y % (panel_rows_ / 2)) >= panel_rows_ / 4; | |
| 411 | ✗ | *matrix_x = is_top_stripe ? x + panel_cols_ : x; | |
| 412 | ✗ | *matrix_y = ((y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4)); | |
| 413 | ✗ | } | |
| 414 | }; | ||
| 415 | |||
| 416 | class CheckeredMultiplexMapper : public MultiplexMapperBase { | ||
| 417 | public: | ||
| 418 | ✗ | CheckeredMultiplexMapper() : | |
| 419 | ✗ | MultiplexMapperBase("Checkered", 2) {} | |
| 420 | |||
| 421 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 422 | ✗ | const bool is_top_check = (y % (panel_rows_ / 2)) < panel_rows_ / 4; | |
| 423 | ✗ | const bool is_left_check = (x < panel_cols_ / 2); | |
| 424 | ✗ | if (is_top_check) { | |
| 425 | ✗ | *matrix_x = is_left_check ? x + panel_cols_ / 2 : x + panel_cols_; | |
| 426 | } else { | ||
| 427 | ✗ | *matrix_x = is_left_check ? x : x + panel_cols_ / 2; | |
| 428 | } | ||
| 429 | ✗ | *matrix_y = ((y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4)); | |
| 430 | ✗ | } | |
| 431 | }; | ||
| 432 | |||
| 433 | class SpiralMultiplexMapper : public MultiplexMapperBase { | ||
| 434 | public: | ||
| 435 | ✗ | SpiralMultiplexMapper() : | |
| 436 | ✗ | MultiplexMapperBase("Spiral", 2) {} | |
| 437 | |||
| 438 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 439 | ✗ | const bool is_top_stripe = (y % (panel_rows_ / 2)) < panel_rows_ / 4; | |
| 440 | ✗ | const int panel_quarter = panel_cols_ / 4; | |
| 441 | ✗ | const int quarter = x / panel_quarter; | |
| 442 | ✗ | const int offset = x % panel_quarter; | |
| 443 | ✗ | *matrix_x = ((2 * quarter * panel_quarter) + (is_top_stripe | |
| 444 | ✗ | ? panel_quarter - 1 - offset | |
| 445 | : panel_quarter + offset)); | ||
| 446 | ✗ | *matrix_y = ((y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4)); | |
| 447 | ✗ | } | |
| 448 | }; | ||
| 449 | |||
| 450 | class ZStripeMultiplexMapper : public MultiplexMapperBase { | ||
| 451 | public: | ||
| 452 | ✗ | ZStripeMultiplexMapper(const char* name, int even_vblock_offset, int odd_vblock_offset) : | |
| 453 | MultiplexMapperBase(name, 2), | ||
| 454 | ✗ | even_vblock_offset_(even_vblock_offset), | |
| 455 | ✗ | odd_vblock_offset_(odd_vblock_offset) {} | |
| 456 | |||
| 457 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 458 | static const int tile_width = 8; | ||
| 459 | static const int tile_height = 4; | ||
| 460 | |||
| 461 | ✗ | const int vert_block_is_odd = ((y / tile_height) % 2); | |
| 462 | |||
| 463 | ✗ | const int even_vblock_shift = (1 - vert_block_is_odd) * even_vblock_offset_; | |
| 464 | ✗ | const int odd_vblock_shitf = vert_block_is_odd * odd_vblock_offset_; | |
| 465 | |||
| 466 | ✗ | *matrix_x = x + ((x + even_vblock_shift) / tile_width) * tile_width + odd_vblock_shitf; | |
| 467 | ✗ | *matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2)); | |
| 468 | ✗ | } | |
| 469 | |||
| 470 | private: | ||
| 471 | const int even_vblock_offset_; | ||
| 472 | const int odd_vblock_offset_; | ||
| 473 | }; | ||
| 474 | |||
| 475 | class CoremanMapper : public MultiplexMapperBase { | ||
| 476 | public: | ||
| 477 | ✗ | CoremanMapper() : | |
| 478 | ✗ | MultiplexMapperBase("coreman", 2) {} | |
| 479 | |||
| 480 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 481 | ✗ | const bool is_left_check = (x < panel_cols_ / 2); | |
| 482 | |||
| 483 | ✗ | if ((y <= 7) || ((y >= 16) && (y <= 23))) { | |
| 484 | ✗ | *matrix_x = ((x / (panel_cols_ / 2)) * panel_cols_) + (x % (panel_cols_ / 2)); | |
| 485 | ✗ | if ((y & (panel_rows_ / 4)) == 0) { | |
| 486 | ✗ | *matrix_y = (y / (panel_rows_ / 2)) * (panel_rows_ / 4) + (y % (panel_rows_ / 4)); | |
| 487 | } | ||
| 488 | } else { | ||
| 489 | ✗ | *matrix_x = is_left_check ? x + panel_cols_ / 2 : x + panel_cols_; | |
| 490 | ✗ | *matrix_y = (y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4); | |
| 491 | } | ||
| 492 | ✗ | } | |
| 493 | }; | ||
| 494 | |||
| 495 | class Kaler2ScanMapper : public MultiplexMapperBase { | ||
| 496 | public: | ||
| 497 | ✗ | Kaler2ScanMapper() : | |
| 498 | ✗ | MultiplexMapperBase("Kaler2Scan", 4) {} | |
| 499 | |||
| 500 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 501 | // Now we have a 128x4 matrix | ||
| 502 | ✗ | int offset = ((y % 4) / 2) == 0 ? -1 : 1; // Add o substract | |
| 503 | ✗ | int deltaOffset = offset < 0 ? 7 : 8; | |
| 504 | ✗ | int deltaColumn = ((y % 8) / 4) == 0 ? 64 : 0; | |
| 505 | |||
| 506 | ✗ | *matrix_y = (y % 2 + (y / 8) * 2); | |
| 507 | ✗ | *matrix_x = deltaColumn + (16 * (x / 8)) + deltaOffset + ((x % 8) * offset); | |
| 508 | ✗ | } | |
| 509 | }; | ||
| 510 | |||
| 511 | class P10MapperZ : public MultiplexMapperBase { | ||
| 512 | public: | ||
| 513 | ✗ | P10MapperZ() : | |
| 514 | ✗ | MultiplexMapperBase("P10-128x4-Z", 4) {} | |
| 515 | // supports this panel: https://www.aliexpress.com/item/2017-Special-Offer-P10-Outdoor-Smd-Full-Color-Led-Display-Module-320x160mm-1-2-Scan-Outdoor/32809267439.html?spm=a2g0s.9042311.0.0.Ob0jEw | ||
| 516 | // with --led-row-addr-type=2 flag | ||
| 517 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 518 | ✗ | int yComp = 0; | |
| 519 | ✗ | if (y == 0 || y == 1 || y == 8 || y == 9) { | |
| 520 | ✗ | yComp = 127; | |
| 521 | ✗ | } else if (y == 2 || y == 3 || y == 10 || y == 11) { | |
| 522 | ✗ | yComp = 112; | |
| 523 | ✗ | } else if (y == 4 || y == 5 || y == 12 || y == 13) { | |
| 524 | ✗ | yComp = 111; | |
| 525 | ✗ | } else if (y == 6 || y == 7 || y == 14 || y == 15) { | |
| 526 | ✗ | yComp = 96; | |
| 527 | } | ||
| 528 | |||
| 529 | ✗ | if (y == 0 || y == 1 || y == 4 || y == 5 || | |
| 530 | ✗ | y == 8 || y == 9 || y == 12 || y == 13) { | |
| 531 | ✗ | *matrix_x = yComp - x; | |
| 532 | ✗ | *matrix_x -= (24 * ((int)(x / 8))); | |
| 533 | } else { | ||
| 534 | ✗ | *matrix_x = yComp + x; | |
| 535 | ✗ | *matrix_x -= (40 * ((int)(x / 8))); | |
| 536 | } | ||
| 537 | |||
| 538 | ✗ | if (y == 0 || y == 2 || y == 4 || y == 6) { | |
| 539 | ✗ | *matrix_y = 3; | |
| 540 | ✗ | } else if (y == 1 || y == 3 || y == 5 || y == 7) { | |
| 541 | ✗ | *matrix_y = 2; | |
| 542 | ✗ | } else if (y == 8 || y == 10 || y == 12 || y == 14) { | |
| 543 | ✗ | *matrix_y = 1; | |
| 544 | ✗ | } else if (y == 9 || y == 11 || y == 13 || y == 15) { | |
| 545 | ✗ | *matrix_y = 0; | |
| 546 | } | ||
| 547 | ✗ | } | |
| 548 | }; | ||
| 549 | |||
| 550 | class QiangLiQ8 : public MultiplexMapperBase { | ||
| 551 | public: | ||
| 552 | ✗ | QiangLiQ8() : | |
| 553 | ✗ | MultiplexMapperBase("QiangLiQ8", 2) {} | |
| 554 | |||
| 555 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 556 | ✗ | const int column = x + (4 + 4 * (x / 4)); | |
| 557 | ✗ | *matrix_x = column; | |
| 558 | ✗ | if ((y >= 15 && y <= 19) || (y >= 5 && y <= 9)) { | |
| 559 | ✗ | const int reverseColumn = x + (4 * (x / 4)); | |
| 560 | ✗ | *matrix_x = reverseColumn; | |
| 561 | } | ||
| 562 | ✗ | *matrix_y = y % 5 + (y / 10) * 5; | |
| 563 | ✗ | } | |
| 564 | }; | ||
| 565 | |||
| 566 | class InversedZStripe : public MultiplexMapperBase { | ||
| 567 | public: | ||
| 568 | ✗ | InversedZStripe() : | |
| 569 | ✗ | MultiplexMapperBase("InversedZStripe", 2) {} | |
| 570 | |||
| 571 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 572 | static const int tile_width = 8; | ||
| 573 | static const int tile_height = 4; | ||
| 574 | |||
| 575 | ✗ | const int vert_block_is_odd = ((y / tile_height) % 2); | |
| 576 | ✗ | const int evenOffset[8] = { 7, 5, 3, 1, -1, -3, -5, -7 }; | |
| 577 | |||
| 578 | ✗ | if (vert_block_is_odd) { | |
| 579 | ✗ | *matrix_x = x + (x / tile_width) * tile_width; | |
| 580 | } else { | ||
| 581 | ✗ | *matrix_x = x + (x / tile_width) * tile_width + 8 + evenOffset[x % 8]; | |
| 582 | } | ||
| 583 | ✗ | *matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2)); | |
| 584 | ✗ | } | |
| 585 | }; | ||
| 586 | |||
| 587 | /* | ||
| 588 | * Vairous P10 1R1G1B Outdoor implementations for 16x16 modules with separate | ||
| 589 | * RGB LEDs, e.g.: | ||
| 590 | * https://www.ledcontrollercard.com/english/p10-outdoor-rgb-led-module-160x160mm-dip.html | ||
| 591 | * | ||
| 592 | */ | ||
| 593 | class P10Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase { | ||
| 594 | public: | ||
| 595 | ✗ | P10Outdoor1R1G1BMultiplexBase(const char* name) : | |
| 596 | ✗ | MultiplexMapperBase(name, 2) {} | |
| 597 | |||
| 598 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 599 | ✗ | const int vblock_is_odd = (y / tile_height_) % 2; | |
| 600 | ✗ | const int vblock_is_even = 1 - vblock_is_odd; | |
| 601 | ✗ | const int even_vblock_shift = vblock_is_even * even_vblock_offset_; | |
| 602 | ✗ | const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_; | |
| 603 | |||
| 604 | ✗ | MapPanel(x, y, matrix_x, matrix_y, | |
| 605 | vblock_is_even, vblock_is_odd, | ||
| 606 | even_vblock_shift, odd_vblock_shift); | ||
| 607 | ✗ | } | |
| 608 | |||
| 609 | protected: | ||
| 610 | virtual void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | ||
| 611 | int vblock_is_even, int vblock_is_odd, | ||
| 612 | int even_vblock_shift, int odd_vblock_shift) const = 0; | ||
| 613 | |||
| 614 | static const int tile_width_ = 8; | ||
| 615 | static const int tile_height_ = 4; | ||
| 616 | static const int even_vblock_offset_ = 0; | ||
| 617 | static const int odd_vblock_offset_ = 8; | ||
| 618 | }; | ||
| 619 | |||
| 620 | class P10Outdoor1R1G1BMultiplexMapper1 : public P10Outdoor1R1G1BMultiplexBase { | ||
| 621 | public: | ||
| 622 | ✗ | P10Outdoor1R1G1BMultiplexMapper1() : | |
| 623 | ✗ | P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-1") {} | |
| 624 | |||
| 625 | protected: | ||
| 626 | ✗ | void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | |
| 627 | const int vblock_is_even, const int vblock_is_odd, | ||
| 628 | const int even_vblock_shift, const int odd_vblock_shift) const { | ||
| 629 | ✗ | *matrix_x = tile_width_ * (1 + vblock_is_even + 2 * (x / tile_width_)) - (x % tile_width_) - 1; | |
| 630 | ✗ | *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2)); | |
| 631 | ✗ | } | |
| 632 | }; | ||
| 633 | |||
| 634 | class P10Outdoor1R1G1BMultiplexMapper2 : public P10Outdoor1R1G1BMultiplexBase { | ||
| 635 | public: | ||
| 636 | ✗ | P10Outdoor1R1G1BMultiplexMapper2() : | |
| 637 | ✗ | P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-2") {} | |
| 638 | |||
| 639 | protected: | ||
| 640 | ✗ | void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | |
| 641 | const int vblock_is_even, const int vblock_is_odd, | ||
| 642 | const int even_vblock_shift, const int odd_vblock_shift) const { | ||
| 643 | ✗ | *matrix_x = vblock_is_even | |
| 644 | ✗ | ? tile_width_ * (1 + 2 * (x / tile_width_)) - (x % tile_width_) - 1 | |
| 645 | ✗ | : x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift; | |
| 646 | ✗ | *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2)); | |
| 647 | ✗ | } | |
| 648 | }; | ||
| 649 | |||
| 650 | class P10Outdoor1R1G1BMultiplexMapper3 : public P10Outdoor1R1G1BMultiplexBase { | ||
| 651 | public: | ||
| 652 | ✗ | P10Outdoor1R1G1BMultiplexMapper3() : | |
| 653 | ✗ | P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-3") {} | |
| 654 | |||
| 655 | protected: | ||
| 656 | ✗ | void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | |
| 657 | const int vblock_is_even, const int vblock_is_odd, | ||
| 658 | const int even_vblock_shift, const int odd_vblock_shift) const { | ||
| 659 | ✗ | *matrix_x = vblock_is_odd | |
| 660 | ✗ | ? tile_width_ * (2 + 2 * (x / tile_width_)) - (x % tile_width_) - 1 | |
| 661 | ✗ | : x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift; | |
| 662 | ✗ | *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2)); | |
| 663 | ✗ | } | |
| 664 | }; | ||
| 665 | |||
| 666 | class P10CoremanMapper : public MultiplexMapperBase { | ||
| 667 | public: | ||
| 668 | ✗ | P10CoremanMapper() : | |
| 669 | ✗ | MultiplexMapperBase("P10CoremanMapper", 4) {} | |
| 670 | |||
| 671 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 672 | // Row offset 8,8,8,8,0,0,0,0,8,8,8,8,0,0,0,0 | ||
| 673 | ✗ | int mulY = (y & 4) > 0 ? 0 : 8; | |
| 674 | |||
| 675 | // Row offset 9,9,8,8,1,1,0,0,9,9,8,8,1,1,0,0 | ||
| 676 | ✗ | mulY += (y & 2) > 0 ? 0 : 1; | |
| 677 | ✗ | mulY += (x >> 2) & ~1; // Drop lsb | |
| 678 | |||
| 679 | ✗ | *matrix_x = (mulY << 3) + x % 8; | |
| 680 | ✗ | *matrix_y = (y & 1) + ((y >> 2) & ~1); | |
| 681 | ✗ | } | |
| 682 | }; | ||
| 683 | |||
| 684 | /* | ||
| 685 | * P8 1R1G1B Outdoor P8-5S-V3.2-HX 20x40 | ||
| 686 | */ | ||
| 687 | class P8Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase { | ||
| 688 | public: | ||
| 689 | ✗ | P8Outdoor1R1G1BMultiplexBase(const char* name) : | |
| 690 | ✗ | MultiplexMapperBase(name, 2) {} | |
| 691 | |||
| 692 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 693 | ✗ | const int vblock_is_odd = (y / tile_height_) % 2; | |
| 694 | ✗ | const int vblock_is_even = 1 - vblock_is_odd; | |
| 695 | ✗ | const int even_vblock_shift = vblock_is_even * even_vblock_offset_; | |
| 696 | ✗ | const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_; | |
| 697 | |||
| 698 | ✗ | MapPanel(x, y, matrix_x, matrix_y, | |
| 699 | vblock_is_even, vblock_is_odd, | ||
| 700 | even_vblock_shift, odd_vblock_shift); | ||
| 701 | ✗ | } | |
| 702 | |||
| 703 | protected: | ||
| 704 | virtual void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | ||
| 705 | int vblock_is_even, int vblock_is_odd, | ||
| 706 | int even_vblock_shift, int odd_vblock_shift) const = 0; | ||
| 707 | |||
| 708 | static const int tile_width_ = 8; | ||
| 709 | static const int tile_height_ = 5; | ||
| 710 | static const int even_vblock_offset_ = 0; | ||
| 711 | static const int odd_vblock_offset_ = 8; | ||
| 712 | }; | ||
| 713 | |||
| 714 | class P8Outdoor1R1G1BMultiplexMapper : public P8Outdoor1R1G1BMultiplexBase { | ||
| 715 | public: | ||
| 716 | ✗ | P8Outdoor1R1G1BMultiplexMapper() : | |
| 717 | ✗ | P8Outdoor1R1G1BMultiplexBase("P8Outdoor1R1G1") {} | |
| 718 | |||
| 719 | protected: | ||
| 720 | ✗ | void MapPanel(int x, int y, int* matrix_x, int* matrix_y, | |
| 721 | const int vblock_is_even, const int vblock_is_odd, | ||
| 722 | const int even_vblock_shift, const int odd_vblock_shift) const { | ||
| 723 | ✗ | *matrix_x = vblock_is_even | |
| 724 | ✗ | ? tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) + tile_width_ - (x % tile_width_) - 1 | |
| 725 | ✗ | : tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) - tile_width_ + (x % tile_width_); | |
| 726 | |||
| 727 | ✗ | *matrix_y = (tile_height_ - y % tile_height_) + tile_height_ * (1 - y / (tile_height_ * 2)) - 1; | |
| 728 | ✗ | } | |
| 729 | }; | ||
| 730 | |||
| 731 | class P10Outdoor32x16HalfScanMapper : public MultiplexMapperBase { | ||
| 732 | public: | ||
| 733 | ✗ | P10Outdoor32x16HalfScanMapper() : | |
| 734 | ✗ | MultiplexMapperBase("P10Outdoor32x16HalfScan", 4) {} | |
| 735 | |||
| 736 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 737 | ✗ | int base = (x / 8) * 32; | |
| 738 | ✗ | bool reverse = (y % 4) / 2 == 0; | |
| 739 | ✗ | int offset = (3 - ((y % 8) / 2)) * 8; | |
| 740 | ✗ | int dx = x % 8; | |
| 741 | |||
| 742 | ✗ | *matrix_y = (y / 8 == 0) ? ((y % 2 == 0) ? 0 : 1) : ((y % 2 == 0) ? 2 : 3); | |
| 743 | ✗ | *matrix_x = base + (reverse ? offset + (7 - dx) : offset + dx); | |
| 744 | ✗ | } | |
| 745 | }; | ||
| 746 | |||
| 747 | class P10Outdoor32x16QuarterScanMapper : public MultiplexMapperBase { | ||
| 748 | public: | ||
| 749 | ✗ | P10Outdoor32x16QuarterScanMapper() : | |
| 750 | ✗ | MultiplexMapperBase("P10Outdoor32x16QuarterScanMapper", 4) {} | |
| 751 | // P10 quarter scan panel, e.g. https://www.ebay.com.au/itm/175517677191 | ||
| 752 | |||
| 753 | ✗ | void EditColsRows(int* cols, int* rows) const { | |
| 754 | ✗ | panel_rows_ = *rows; | |
| 755 | ✗ | panel_cols_ = *cols; | |
| 756 | |||
| 757 | ✗ | *rows /= panel_stretch_factor_ / 2; // has half stretch factor in y compared to x | |
| 758 | ✗ | *cols *= panel_stretch_factor_; | |
| 759 | ✗ | } | |
| 760 | |||
| 761 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 762 | ✗ | int cell_starting_point = (x / 8) * 32; | |
| 763 | ✗ | int delta_x = x % 8; | |
| 764 | ✗ | int offset = (3 - (y / 4)) * 8; | |
| 765 | ✗ | *matrix_x = cell_starting_point + delta_x + offset; | |
| 766 | ✗ | *matrix_y = y % 4; | |
| 767 | ✗ | } | |
| 768 | }; | ||
| 769 | |||
| 770 | class P3Outdoor64x64MultiplexMapper : public MultiplexMapperBase { | ||
| 771 | public: | ||
| 772 | ✗ | P3Outdoor64x64MultiplexMapper() : | |
| 773 | ✗ | MultiplexMapperBase("P3Outdoor64x64MultiplexMapper", 2) {} | |
| 774 | // P3 RGB panel 64x64 | ||
| 775 | // with pattern [1] [3] | ||
| 776 | // | \ | | ||
| 777 | // [0] [2] | ||
| 778 | |||
| 779 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 780 | ✗ | const bool is_top_stripe = (y % (panel_rows_ / 2)) < panel_rows_ / 4; | |
| 781 | ✗ | *matrix_x = ((x * 2) + (is_top_stripe ? 1 : 0)); | |
| 782 | ✗ | *matrix_y = ((y / (panel_rows_ / 2)) * (panel_rows_ / 4) + y % (panel_rows_ / 4)); | |
| 783 | ✗ | } | |
| 784 | }; | ||
| 785 | |||
| 786 | class DoubleZMultiplexMapper : public MultiplexMapperBase { | ||
| 787 | public: | ||
| 788 | ✗ | DoubleZMultiplexMapper() : | |
| 789 | ✗ | MultiplexMapperBase("DoubleZ", 2) {} | |
| 790 | |||
| 791 | ✗ | void MapSinglePanel(int x, int y, int* matrix_x, int* matrix_y) const { | |
| 792 | ✗ | const int quarter_rows = panel_rows_ / 4; | |
| 793 | ✗ | const int quarter_cols = panel_cols_ / 4; | |
| 794 | |||
| 795 | ✗ | const int y_quarter = y / quarter_rows; | |
| 796 | ✗ | const int x_quarter = x / quarter_cols; | |
| 797 | |||
| 798 | ✗ | const int offset_y = y % quarter_rows; | |
| 799 | ✗ | int offset_x = x % quarter_cols; | |
| 800 | |||
| 801 | ✗ | const bool flip_quarter = (y_quarter == 1 || y_quarter == 3); | |
| 802 | ✗ | if (flip_quarter) { | |
| 803 | ✗ | offset_x = quarter_cols - 1 - offset_x; | |
| 804 | } | ||
| 805 | |||
| 806 | ✗ | const bool is_top_stripe = !((y % (panel_rows_ / 2)) < quarter_rows); | |
| 807 | |||
| 808 | ✗ | const int base_x = 2 * x_quarter * quarter_cols; | |
| 809 | ✗ | const int top_offset = is_top_stripe | |
| 810 | ✗ | ? (quarter_cols - 1 - offset_x) | |
| 811 | : (quarter_cols + offset_x); | ||
| 812 | |||
| 813 | ✗ | *matrix_x = base_x + top_offset; | |
| 814 | ✗ | *matrix_y = (y_quarter / 2) * quarter_rows + offset_y; | |
| 815 | ✗ | } | |
| 816 | }; | ||
| 817 | |||
| 818 | class PiRGBInterleaveHandler : public PanelInterleaveHandler { | ||
| 819 | public: | ||
| 820 | ✗ | PiRGBInterleaveHandler(int pw, int ph, int ps, int pi) : | |
| 821 | ✗ | PanelInterleaveHandler(pw, ph, ps, 8) { | |
| 822 | // buildHandler only passes the indexes handled here | ||
| 823 | ✗ | MultiplexMapperBase* base = nullptr; | |
| 824 | ✗ | switch (pi) { | |
| 825 | ✗ | case 1: | |
| 826 | ✗ | base = new StripeMultiplexMapper(); | |
| 827 | ✗ | break; | |
| 828 | ✗ | case 2: | |
| 829 | ✗ | base = new CheckeredMultiplexMapper(); | |
| 830 | ✗ | break; | |
| 831 | ✗ | case 3: | |
| 832 | ✗ | base = new SpiralMultiplexMapper(); | |
| 833 | ✗ | break; | |
| 834 | ✗ | case 4: | |
| 835 | ✗ | base = new ZStripeMultiplexMapper("ZStripe", 0, 8); | |
| 836 | ✗ | break; | |
| 837 | ✗ | case 5: | |
| 838 | ✗ | base = new ZStripeMultiplexMapper("ZnMirrorZStripe", 4, 4); | |
| 839 | ✗ | break; | |
| 840 | ✗ | case 6: | |
| 841 | ✗ | base = new CoremanMapper(); | |
| 842 | ✗ | break; | |
| 843 | ✗ | case 7: | |
| 844 | ✗ | base = new Kaler2ScanMapper(); | |
| 845 | ✗ | break; | |
| 846 | ✗ | case 8: | |
| 847 | ✗ | base = new ZStripeMultiplexMapper("ZStripeUneven", 8, 0); | |
| 848 | ✗ | break; | |
| 849 | ✗ | case 9: | |
| 850 | ✗ | base = new P10MapperZ(); | |
| 851 | ✗ | break; | |
| 852 | ✗ | case 10: | |
| 853 | ✗ | base = new QiangLiQ8(); | |
| 854 | ✗ | break; | |
| 855 | ✗ | case 11: | |
| 856 | ✗ | base = new InversedZStripe(); | |
| 857 | ✗ | break; | |
| 858 | ✗ | case 12: | |
| 859 | ✗ | base = new P10Outdoor1R1G1BMultiplexMapper1(); | |
| 860 | ✗ | break; | |
| 861 | ✗ | case 13: | |
| 862 | ✗ | base = new P10Outdoor1R1G1BMultiplexMapper2(); | |
| 863 | ✗ | break; | |
| 864 | ✗ | case 14: | |
| 865 | ✗ | base = new P10Outdoor1R1G1BMultiplexMapper3(); | |
| 866 | ✗ | break; | |
| 867 | ✗ | case 15: | |
| 868 | ✗ | base = new P10CoremanMapper(); | |
| 869 | ✗ | break; | |
| 870 | ✗ | case 16: | |
| 871 | ✗ | base = new P8Outdoor1R1G1BMultiplexMapper(); | |
| 872 | ✗ | break; | |
| 873 | ✗ | case 17: | |
| 874 | ✗ | base = new FlippedStripeMultiplexMapper(); | |
| 875 | ✗ | break; | |
| 876 | ✗ | case 18: | |
| 877 | ✗ | base = new P10Outdoor32x16HalfScanMapper(); | |
| 878 | ✗ | break; | |
| 879 | ✗ | case 19: | |
| 880 | ✗ | base = new P10Outdoor32x16QuarterScanMapper(); | |
| 881 | ✗ | break; | |
| 882 | ✗ | case 20: | |
| 883 | ✗ | base = new P3Outdoor64x64MultiplexMapper(); | |
| 884 | ✗ | break; | |
| 885 | ✗ | case 21: | |
| 886 | ✗ | base = new DoubleZMultiplexMapper(); | |
| 887 | ✗ | break; | |
| 888 | } | ||
| 889 | ✗ | base->EditColsRows(&pw, &ph); | |
| 890 | ✗ | for (int y = 0; y < m_panelHeight; ++y) { | |
| 891 | ✗ | for (int x = 0; x < m_panelWidth; ++x) { | |
| 892 | ✗ | int idx = (y * m_panelWidth + x); | |
| 893 | |||
| 894 | ✗ | int x1 = x; | |
| 895 | ✗ | int y1 = y; | |
| 896 | ✗ | base->MapVisibleToMatrix(m_panelWidth, m_panelHeight, x1, y1, &x1, &y1); | |
| 897 | ✗ | m_map[idx] = std::make_pair(x1, y1); | |
| 898 | } | ||
| 899 | } | ||
| 900 | ✗ | delete base; | |
| 901 | ✗ | } | |
| 902 | ✗ | virtual ~PiRGBInterleaveHandler() {} | |
| 903 | |||
| 904 | // the rpi-rgb-led-matrix multiplex mappers this handler can wrap | ||
| 905 | static constexpr int NUM_MAPPERS = 21; | ||
| 906 | }; | ||
| 907 | |||
| 908 | 15 | static PanelInterleaveHandler* buildHandler(const std::string& type, int panelWidth, int panelHeight, int panelScan) { | |
| 909 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | if ((panelScan * 2) == panelHeight) { |
| 910 |
2/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
15 | return new NoInterleaveHandler(panelWidth, panelHeight, panelScan, 0); |
| 911 | } | ||
| 912 | ✗ | if (type == "4z") { | |
| 913 | ✗ | return new ZigZagInterleaveHandler(panelWidth, panelHeight, panelScan, 4); | |
| 914 | ✗ | } else if (type == "8z") { | |
| 915 | ✗ | return new ZigZagInterleaveHandler(panelWidth, panelHeight, panelScan, 8); | |
| 916 | ✗ | } else if (type == "16z") { | |
| 917 | ✗ | return new ZigZagInterleaveHandler(panelWidth, panelHeight, panelScan, 16); | |
| 918 | ✗ | } else if (type == "32z") { | |
| 919 | ✗ | return new ZigZagInterleaveHandler(panelWidth, panelHeight, panelScan, 32); | |
| 920 | ✗ | } else if (type == "40z") { | |
| 921 | ✗ | return new ZigZagInterleaveHandler(panelWidth, panelHeight, panelScan, 40); | |
| 922 | ✗ | } else if (type == "1f") { | |
| 923 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 1, true); | |
| 924 | ✗ | } else if (type == "2f") { | |
| 925 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 2, true); | |
| 926 | ✗ | } else if (type == "4f") { | |
| 927 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 4, true); | |
| 928 | ✗ | } else if (type == "8f") { | |
| 929 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 8, true); | |
| 930 | ✗ | } else if (type == "16f") { | |
| 931 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 16, true); | |
| 932 | ✗ | } else if (type == "32f") { | |
| 933 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 32, true); | |
| 934 | ✗ | } else if (type == "40f") { | |
| 935 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 40, true); | |
| 936 | ✗ | } else if (type == "64f") { | |
| 937 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 64, true); | |
| 938 | ✗ | } else if (type == "80f") { | |
| 939 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 80, true); | |
| 940 | ✗ | } else if (type == "1") { | |
| 941 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 1, false); | |
| 942 | ✗ | } else if (type == "2") { | |
| 943 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 2, false); | |
| 944 | ✗ | } else if (type == "4") { | |
| 945 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 4, false); | |
| 946 | ✗ | } else if (type == "8") { | |
| 947 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 8, false); | |
| 948 | ✗ | } else if (type == "16") { | |
| 949 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 16, false); | |
| 950 | ✗ | } else if (type == "32") { | |
| 951 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 32, false); | |
| 952 | ✗ | } else if (type == "40") { | |
| 953 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 40, false); | |
| 954 | ✗ | } else if (type == "64") { | |
| 955 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 64, false); | |
| 956 | ✗ | } else if (type == "80") { | |
| 957 | ✗ | return new SimpleInterleaveHandler(panelWidth, panelHeight, panelScan, 80, false); | |
| 958 | ✗ | } else if (type == "8c") { | |
| 959 | ✗ | return new ZigZagClusterInterleaveHandler(panelWidth, panelHeight, panelScan, 8); | |
| 960 | ✗ | } else if (type == "16c") { | |
| 961 | ✗ | return new ZigZagClusterInterleaveHandler(panelWidth, panelHeight, panelScan, 16); | |
| 962 | ✗ | } else if (type == "8s") { | |
| 963 | ✗ | return new StripeClusterInterleaveHandler(panelWidth, panelHeight, panelScan, 8); | |
| 964 | ✗ | } else if (type == "16s") { | |
| 965 | ✗ | return new StripeClusterInterleaveHandler(panelWidth, panelHeight, panelScan, 16); | |
| 966 | ✗ | } else if (type.starts_with("RPi")) { | |
| 967 | ✗ | std::string idx = type.substr(3); | |
| 968 | ✗ | int idxValue = std::atoi(idx.c_str()); | |
| 969 | ✗ | if (idxValue >= 1 && idxValue <= PiRGBInterleaveHandler::NUM_MAPPERS) { | |
| 970 | ✗ | return new PiRGBInterleaveHandler(panelWidth, panelHeight, panelScan, idxValue); | |
| 971 | } | ||
| 972 | // an unknown index falls back like any other unknown type | ||
| 973 | ✗ | } | |
| 974 | |||
| 975 | ✗ | return new NoInterleaveHandler(panelWidth, panelHeight, panelScan, 0); | |
| 976 | } | ||
| 977 | |||
| 978 | 15 | PanelInterleaveHandler* PanelInterleaveHandler::createHandler(const std::string& type, int panelWidth, int panelHeight, int panelScan) { | |
| 979 | // The handlers fold rows by the scan as they are built (a scan of 0 or | ||
| 980 | // below loops forever in mapRow), so this cannot wait for mapFitsFrame(). | ||
| 981 | // The UI only offers valid sizes; a hand-edited config may not. | ||
| 982 |
4/8✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 15 times.
|
15 | if (panelWidth <= 0 || panelHeight <= 0 || panelScan <= 0 || panelScan > panelHeight) { |
| 983 | ✗ | return nullptr; | |
| 984 | } | ||
| 985 | 15 | PanelInterleaveHandler* handler = buildHandler(type, panelWidth, panelHeight, panelScan); | |
| 986 |
3/6✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
|
15 | if (handler && !handler->mapFitsFrame()) { |
| 987 | // Unknown interleave types fall back to no interleave at all, which | ||
| 988 | // is only a valid mapping when the panel scans half its rows. On a | ||
| 989 | // panel that scans fewer, the unfolded rows index past the end of the | ||
| 990 | // frame buffer, so refuse the handler and let the caller disable the | ||
| 991 | // output instead of corrupting the heap on the first frame. | ||
| 992 | ✗ | delete handler; | |
| 993 | ✗ | return nullptr; | |
| 994 | } | ||
| 995 | 15 | return handler; | |
| 996 | } | ||
| 997 | |||
| 998 | #ifdef STANDALONE | ||
| 999 | |||
| 1000 | int main(int argc, char* argv[]) { | ||
| 1001 | // Example usage of PanelInterleaveHandler | ||
| 1002 | int panelWidth = 32; | ||
| 1003 | int panelHeight = 32; | ||
| 1004 | int panelScan = 8; | ||
| 1005 | |||
| 1006 | PanelInterleaveHandler* handler = PanelInterleaveHandler::createHandler(argv[1], panelWidth, panelHeight, panelScan); | ||
| 1007 | |||
| 1008 | for (int x = 0; x < panelWidth; ++x) { | ||
| 1009 | int xOut = x; | ||
| 1010 | int yOut = 0; | ||
| 1011 | handler->map(xOut, yOut); | ||
| 1012 | printf("%d %d -> %d %d ", x, 0, xOut, yOut); | ||
| 1013 | xOut = x; | ||
| 1014 | yOut = 8; | ||
| 1015 | handler->map(xOut, yOut); | ||
| 1016 | printf("%d %d -> %d %d\n", x, 8, xOut, yOut); | ||
| 1017 | } | ||
| 1018 | int x = 5, y = 3; | ||
| 1019 | handler->map(x, y); | ||
| 1020 | |||
| 1021 | delete handler; | ||
| 1022 | return 0; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | #endif | ||
| 1026 |