forked from portapack-mayhem/mayhem-firmware
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlcd_ili9341.cpp
More file actions
839 lines (718 loc) · 28.6 KB
/
Copy pathlcd_ili9341.cpp
File metadata and controls
839 lines (718 loc) · 28.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "lcd_ili9341.hpp"
#include "bmp.hpp"
#include "portapack_io.hpp"
using namespace portapack;
#include "utility.hpp"
#include "ch.h"
#include "file.hpp"
#include "portapack_persistent_memory.hpp"
#include <complex>
#include <cstring>
#include <string>
namespace lcd {
namespace {
void lcd_reset() {
io.lcd_reset_state(false);
chThdSleepMilliseconds(1);
io.lcd_reset_state(true);
chThdSleepMilliseconds(10);
io.lcd_reset_state(false);
chThdSleepMilliseconds(120);
}
void lcd_sleep_in() {
io.lcd_data_write_command_and_data(0x10, {});
// "It will be necessary to wait 5msec before sending next command,
// this is to allow time for the supply voltages and clock circuits
// to stabilize."
chThdSleepMilliseconds(5);
}
void lcd_sleep_out() {
io.lcd_data_write_command_and_data(0x11, {});
// "It will be necessary to wait 120msec after sending Sleep Out
// command (when in Sleep In Mode) before Sleep In command can be
// sent."
chThdSleepMilliseconds(120);
}
void lcd_display_on() {
io.lcd_data_write_command_and_data(0x29, {});
}
void lcd_display_off() {
io.lcd_data_write_command_and_data(0x28, {});
}
void lcd_sleep(bool hw_sleep = true) {
lcd_display_off();
if (hw_sleep) {
lcd_sleep_in();
}
}
void lcd_wake(bool hw_sleep = true) {
if (hw_sleep) {
lcd_sleep_out();
}
lcd_display_on();
}
uint32_t lcd_read_display_status() {
io.lcd_data_write_command_and_data(0x09, {});
io.lcd_read_word();
uint32_t value2 = io.lcd_read_word();
uint32_t value3 = io.lcd_read_word();
uint32_t value4 = io.lcd_read_word();
uint32_t value5 = io.lcd_read_word();
return value5 + (value4 << 8) + (value3 << 16) + (value2 << 24);
}
void lcd_init() {
// LCDs are configured for IM[2:0] = 001
// 8080-I system, 16-bit parallel bus
io.lcd_data_write_command_and_data(0xE0, {0x00, 0x09, 0x0C, 0x03, 0x10, 0x06, 0x34, 0x68,
0x49, 0x02, 0x0A, 0x07, 0x2C, 0x31, 0x0F});
io.lcd_data_write_command_and_data(0xE1, {0x00, 0x12, 0x15, 0x02, 0x10, 0x06, 0x35, 0x35,
0x4A, 0x05, 0x10, 0x0C, 0x2F, 0x33, 0x0F});
io.lcd_data_write_command_and_data(0xC0, {0x0F, 0x0F}); // Vreg1out=4.5 Vreg2out=-4.5
io.lcd_data_write_command_and_data(0xC1, {0x44}); // VGH = 5*VCI VGL = -4*VCI
io.lcd_data_write_command_and_data(0xC5, {0x00, 0x66, 0x80}); // VCOM
io.lcd_data_write_command_and_data(0x36, {
(1 << 7) | // MY=1
(0 << 6) | // MX=0
(0 << 5) | // MV=0
(1 << 4) | // ML=1: reverse vertical refresh to simplify scrolling logic
(1 << 3) // BGR=1: For Kingtech LCD, BGR filter.
});
// io.lcd_data_write_command_and_data(0x36, {0x48});
io.lcd_data_write_command_and_data(0x3A, {0x65}); // 0x55 was the original
// 这里的刷新率过低?
// io.lcd_data_write_command_and_data(0xB1, {0x40, 0x1F}); // fix 60 fps
io.lcd_data_write_command_and_data(0xB1, {0xA0, 0x11}); // Frame rate source is 11fps
io.lcd_data_write_command_and_data(0xB4, {0x02}); // 2 dot inversion
io.lcd_data_write_command_and_data(0xEE, {0x00, 0x04});
io.lcd_data_write_command_and_data(0xE9, {0x00});
io.lcd_data_write_command_and_data(0xF7, {0xA9, 0x51, 0x2C, 0x82});
io.lcd_data_write_command_and_data(0x21, {}); // Display Inversion On
io.lcd_data_write_command_and_data(0x11, {}); // Sleep Out
chThdSleepMilliseconds(120); // Delay 120ms
io.lcd_data_write_command_and_data(0x29, {}); // Display On
chThdSleepMilliseconds(50); // Delay 50ms
// Turn on Tearing Effect Line (TE) output signal.
io.lcd_data_write_command_and_data(0x35, {0b00000000});
}
void lcd_init_pp() {
// LCDs are configured for IM[2:0] = 001
// 8080-I system, 16-bit parallel bus
//
// 0x3a: DBI[2:0] = 101
// MDT[1:0] = XX (if not in 18-bit mode, right?)
// Power control B
// 0
// PCEQ=1, DRV_ena=0, Power control=3
io.lcd_data_write_command_and_data(0xCF, {0x00, 0xD9, 0x30});
// Power on sequence control
io.lcd_data_write_command_and_data(0xED, {0x64, 0x03, 0x12, 0x81});
// Driver timing control A
io.lcd_data_write_command_and_data(0xE8, {0x85, 0x10, 0x78});
// Power control A
io.lcd_data_write_command_and_data(0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02});
// Pump ratio control
io.lcd_data_write_command_and_data(0xF7, {0x20});
// Driver timing control B
io.lcd_data_write_command_and_data(0xEA, {0x00, 0x00});
io.lcd_data_write_command_and_data(0xB1, {0x00, 0x1B});
// Blanking Porch Control
// VFP = 0b0000010 = 2 (number of HSYNC of vertical front porch)
// VBP = 0b0000010 = 2 (number of HSYNC of vertical back porch)
// HFP = 0b0001010 = 10 (number of DOTCLOCK of horizontal front porch)
// HBP = 0b0010100 = 20 (number of DOTCLOCK of horizontal back porch)
io.lcd_data_write_command_and_data(0xB5, {0x02, 0x02, 0x0a, 0x14});
// Display Function Control
// PT[1:0] = 0b10
// PTG[1:0] = 0b10
// ISC[3:0] = 0b0010 (scan cycle interval of gate driver: 5 frames)
// SM = 0 (gate driver pin arrangement in combination with GS)
// SS = 1 (source output scan direction S720 -> S1)
// GS = 0 (gate output scan direction G1 -> G320)
// REV = 1 (normally white)
// NL = 0b100111 (default)
// PCDIV = 0b000000 (default?)
/*as per the datasheet chapter 8.3.7, addr B6h,
data "REV" bit, liquid crystal type:*/
if (portapack::persistent_memory::config_lcd_normally_black())
io.lcd_data_write_command_and_data(0xB6, {0x0A, 0x22, 0x27, 0x00}); // IPS : normally black : 0
else
io.lcd_data_write_command_and_data(0xB6, {0x0A, 0xA2, 0x27, 0x00}); // TFT : normally white : 1
// Power Control 1
// VRH[5:0]
io.lcd_data_write_command_and_data(0xC0, {0x1B});
// Power Control 2
// SAP[2:0];BT[3:0]
io.lcd_data_write_command_and_data(0xC1, {0x12});
// VCOM Control 1
io.lcd_data_write_command_and_data(0xC5, {0x32, 0x3C});
// VCOM Control 2
io.lcd_data_write_command_and_data(0xC7, {0x9B});
// Memory Access Control
// Invert X and Y memory access order, so upper-left of
// screen is (0,0) when writing to display.
io.lcd_data_write_command_and_data(0x36, {
(1 << 7) | // MY=1
(1 << 6) | // MX=1
(0 << 5) | // MV=0
(1 << 4) | // ML=1: reverse vertical refresh to simplify scrolling logic
(1 << 3) // BGR=1: For Kingtech LCD, BGR filter.
});
// COLMOD: Pixel Format Set
// DPI=101 (16 bits/pixel), DBI=101 (16 bits/pixel)
io.lcd_data_write_command_and_data(0x3A, {0x55});
// io.lcd_data_write_command_and_data(0xF6, { 0x01, 0x30 });
// WEMODE=1 (reset column and page number on overflow)
// MDT[1:0]
// EPF[1:0]=00 (use channel MSB for LSB)
// RIM=0 (If COLMOD[6:4]=101 (65k color), 16-bit RGB interface (1 transfer/pixel))
// RM=0 (system interface/VSYNC interface)
// DM[1:0]=00 (internal clock operation)
// ENDIAN=0 (doesn't matter with 16-bit interface)
io.lcd_data_write_command_and_data(0xF6, {0x01, 0x30, 0x00});
// 3Gamma Function Disable
io.lcd_data_write_command_and_data(0xF2, {0x00});
// Gamma curve selected
io.lcd_data_write_command_and_data(0x26, {0x01});
// Set Gamma
io.lcd_data_write_command_and_data(0xE0, {0x0F, 0x1D, 0x19, 0x0E, 0x10, 0x07, 0x4C, 0x63,
0x3F, 0x03, 0x0D, 0x00, 0x26, 0x24, 0x04});
// Set Gamma
io.lcd_data_write_command_and_data(0xE1, {0x00, 0x1C, 0x1F, 0x02, 0x0F, 0x03, 0x35, 0x25,
0x47, 0x04, 0x0C, 0x0B, 0x29, 0x2F, 0x05});
lcd_wake();
// Turn on Tearing Effect Line (TE) output signal.
io.lcd_data_write_command_and_data(0x35, {0b00000000});
}
void lcd_set(const uint_fast8_t command, const uint_fast16_t start, const uint_fast16_t end) {
io.lcd_data_write_command_and_data(command, {static_cast<uint8_t>(start >> 8), static_cast<uint8_t>(start & 0xff),
static_cast<uint8_t>(end >> 8), static_cast<uint8_t>(end & 0xff)});
}
void lcd_ramwr_start() {
io.lcd_data_write_command_and_data(0x2c, {});
}
void lcd_ramrd_start() {
io.lcd_data_write_command_and_data(0x2e, {});
io.lcd_read_word();
}
void lcd_caset(const uint_fast16_t start_column, uint_fast16_t end_column) {
lcd_set(0x2a, start_column, end_column);
}
void lcd_paset(const uint_fast16_t start_page, const uint_fast16_t end_page) {
lcd_set(0x2b, start_page, end_page);
}
void lcd_start_ram_write(
const ui::Point p,
const ui::Size s) {
lcd_caset(p.x(), p.x() + s.width() - 1);
lcd_paset(p.y(), p.y() + s.height() - 1);
lcd_ramwr_start();
}
void lcd_start_ram_read(
const ui::Point p,
const ui::Size s) {
lcd_caset(p.x(), p.x() + s.width() - 1);
lcd_paset(p.y(), p.y() + s.height() - 1);
lcd_ramrd_start();
}
void lcd_start_ram_write(
const ui::Rect& r) {
lcd_start_ram_write(r.location(), r.size());
}
void lcd_start_ram_read(
const ui::Rect& r) {
lcd_start_ram_read(r.location(), r.size());
}
void lcd_vertical_scrolling_definition(
const uint_fast16_t top_fixed_area,
const uint_fast16_t vertical_scrolling_area,
const uint_fast16_t bottom_fixed_area) {
io.lcd_data_write_command_and_data(0x33, {static_cast<uint8_t>(top_fixed_area >> 8),
static_cast<uint8_t>(top_fixed_area & 0xff),
static_cast<uint8_t>(vertical_scrolling_area >> 8),
static_cast<uint8_t>(vertical_scrolling_area & 0xff),
static_cast<uint8_t>(bottom_fixed_area >> 8),
static_cast<uint8_t>(bottom_fixed_area & 0xff)});
}
void lcd_vertical_scrolling_start_address(
const uint_fast16_t vertical_scrolling_pointer) {
io.lcd_data_write_command_and_data(0x37, {static_cast<uint8_t>(vertical_scrolling_pointer >> 8),
static_cast<uint8_t>(vertical_scrolling_pointer & 0xff)});
}
} // namespace
uint32_t ILI9341::lcd_read_display_id() {
io.lcd_data_write_command_and_data(0x04, {});
io.lcd_read_data_raw();
uint32_t value3 = io.lcd_read_data_raw();
uint32_t value4 = io.lcd_read_data_raw();
uint32_t value5 = io.lcd_read_data_raw();
return value5 + (value4 << 8) + (value3 << 16);
}
bool ILI9341::read_display_status() {
lcd_reset();
uint32_t display_status = lcd_read_display_status();
/* This tries to validate the display_status.
* The value could vary from device to device, so we are less specific here.
* 0xFFFFFEFF was seen when the display was not reachable
* 0x00610000 was seen when the display was reachable
*/
if (display_status > 0x0E000000ULL)
return false;
if (display_status < 0x00000100ULL)
return false;
return true;
}
void ILI9341::init() {
lcd_reset();
bool hpp = false;
// detect method 1
uint32_t id = lcd_read_display_id();
if (id == 5537894) hpp = true;
// detect method 2 - not working!
if (!hpp) {
draw_pixel({318, 478}, ui::Color::red());
fill_rectangle_unrolled8({0, 0, 240, 320}, ui::Color::black());
std::vector<ui::ColorRGB888> checker(1);
read_pixels({318, 478, 1, 1}, checker.data(), 1);
if (checker[0].b <= 10 && checker[0].g <= 10 && checker[0].r >= 100)
hpp = true;
}
// init screen
if (hpp) {
device_type = DEV_PORTARF;
lcd_init();
screen_width = 320;
screen_height = 480;
} else {
device_type = DEV_PORTAPACK;
lcd_init_pp();
screen_width = 240;
screen_height = 320;
}
}
void ILI9341::shutdown() {
lcd_reset();
}
void ILI9341::sleep(bool hw_sleep) {
lcd_sleep(hw_sleep);
}
void ILI9341::wake(bool hw_sleep) {
lcd_wake(hw_sleep);
}
void ILI9341::fill_rectangle(ui::Rect r, const ui::Color c) {
const auto r_clipped = r.intersect(screen_rect());
if (!r_clipped.is_empty()) {
lcd_start_ram_write(r_clipped);
size_t count = r_clipped.width() * r_clipped.height();
io.lcd_write_pixels(c, count);
}
}
void ILI9341::fill_rectangle_unrolled8(ui::Rect r, const ui::Color c) {
const auto r_clipped = r.intersect(screen_rect());
if (!r_clipped.is_empty()) {
lcd_start_ram_write(r_clipped);
size_t count = r_clipped.width() * r_clipped.height();
io.lcd_write_pixels_unrolled8(c, count);
}
}
void ILI9341::render_line(const ui::Point p, const uint16_t count, const ui::Color* line_buffer) {
lcd_start_ram_write(p, {count, 1});
io.lcd_write_pixels(line_buffer, count);
}
void ILI9341::render_box(const ui::Point p, const ui::Size s, const ui::Color* line_buffer) {
lcd_start_ram_write(p, s);
io.lcd_write_pixels(line_buffer, s.width() * s.height());
}
// RLE_4 BMP loader (delta not implemented)
/* draw transparent, pass transparent color as arg, usage inline anonymous obj
* portapack::display.draw_bmp_from_bmp_hex_arr({100, 100}, foo_bmp, (const uint8_t[]){41, 24, 22}); // dec, out of {255, 255, 255}
* portapack::display.draw_bmp_from_bmp_hex_arr({100, 100}, foo_bmp, (const uint8_t[]){0x29, 0x18, 0x16}); //hex out of {0xFF, 0xFF, 0xFF}
*
* draw transparent, pass transparent color as arg, usage pass uint8_t[] obj
* const uint8_t c[] = {41, 24, 22}; or const uint8_t c[3] = {0x29, 0x18, 0x16};
* portapack::display.draw_bmp_from_bmp_hex_arr({100, 100}, foo_bmp, c);
*
* don't draw transparent, pass nullptr as arg, usage
* portapack::display.draw_bmp_from_bmp_hex_arr({100, 100}, foo_bmp, nullptr);
*
* if your image use RLE compress as transparency methods, pass any valid color as arg, it doesn't matter (like the modal bmp. TODO: write RLE transparency generator)
* */
void ILI9341::draw_bmp_from_bmp_hex_arr(const ui::Point p, const uint8_t* bitmap, const uint8_t* transparency_color) {
const bmp_header_t* bmp_header = (const bmp_header_t*)bitmap;
uint32_t data_idx;
uint8_t by, c, count, transp_idx = 0;
ui::Color line_buffer[240];
uint16_t px = 0, py;
ui::Color palette[16];
// Abort if bad depth or no RLE
if ((bmp_header->bpp != 4) ||
(bmp_header->compression != 2)) return;
data_idx = bmp_header->image_data;
const bmp_palette_t* bmp_palette = (const bmp_palette_t*)&bitmap[bmp_header->BIH_size + 14];
// Convert palette and find pure magenta index (alpha color key) rgb dec(41,24,22)
for (c = 0; c < 16; c++) {
palette[c] = ui::Color(bmp_palette->color[c].R, bmp_palette->color[c].G, bmp_palette->color[c].B);
if (transparency_color &&
(bmp_palette->color[c].R == transparency_color[0]) &&
(bmp_palette->color[c].G == transparency_color[1]) &&
(bmp_palette->color[c].B == transparency_color[2])) transp_idx = c;
}
if (!transparency_color) {
py = bmp_header->height + 16;
do {
by = bitmap[data_idx++];
if (by) {
count = by >> 1;
by = bitmap[data_idx++];
for (c = 0; c < count; c++) {
line_buffer[px++] = palette[by >> 4];
if (px < bmp_header->width) line_buffer[px++] = palette[by & 15];
}
if (data_idx & 1) data_idx++;
} else {
by = bitmap[data_idx++];
if (by == 0) {
render_line({p.x(), p.y() + py}, bmp_header->width, line_buffer);
py--;
px = 0;
} else if (by == 1) {
break;
} else if (by == 2) {
// Delta
} else {
count = by >> 1;
for (c = 0; c < count; c++) {
by = bitmap[data_idx++];
line_buffer[px++] = palette[by >> 4];
if (px < bmp_header->width) line_buffer[px++] = palette[by & 15];
}
if (data_idx & 1) data_idx++;
}
}
} while (1);
} else {
py = bmp_header->height;
do {
by = bitmap[data_idx++];
if (by) {
count = by >> 1;
by = bitmap[data_idx++];
for (c = 0; c < count; c++) {
if ((by >> 4) != transp_idx) draw_pixel({static_cast<ui::Coord>(p.x() + px), static_cast<ui::Coord>(p.y() + py)}, palette[by >> 4]);
px++;
if (px < bmp_header->width) {
if ((by & 15) != transp_idx) draw_pixel({static_cast<ui::Coord>(p.x() + px), static_cast<ui::Coord>(p.y() + py)}, palette[by & 15]);
}
px++;
}
if (data_idx & 1) data_idx++;
} else {
by = bitmap[data_idx++];
if (by == 0) {
py--;
px = 0;
} else if (by == 1) {
break;
} else if (by == 2) {
// Delta
} else {
count = by >> 1;
for (c = 0; c < count; c++) {
by = bitmap[data_idx++];
if ((by >> 4) != transp_idx) draw_pixel({static_cast<ui::Coord>(p.x() + px), static_cast<ui::Coord>(p.y() + py)}, palette[by >> 4]);
px++;
if (px < bmp_header->width) {
if ((by & 15) != transp_idx) draw_pixel({static_cast<ui::Coord>(p.x() + px), static_cast<ui::Coord>(p.y() + py)}, palette[by & 15]);
}
px++;
}
if (data_idx & 1) data_idx++;
}
}
} while (1);
}
}
/* Draw BMP from SD card.
* Currently supported formats:
* 16bpp ARGB, RGB565
* 24bpp RGB
* 32bpp ARGB
*/
bool ILI9341::draw_bmp_from_sdcard_file(const ui::Point p, const std::filesystem::path& file) {
File bmpimage;
size_t file_pos = 0;
uint16_t pointer = 0;
int16_t px = 0, py, width, height;
bmp_header_t bmp_header;
uint8_t type = 0;
char buffer[257];
ui::Color line_buffer[320];
int16_t start_x = p.x();
auto result = bmpimage.open(file);
if (result.is_valid())
return false;
bmpimage.seek(file_pos);
auto read_size = bmpimage.read(&bmp_header, sizeof(bmp_header));
if (!((bmp_header.signature == 0x4D42) && // "BM" Signature
(bmp_header.planes == 1) && // Seems always to be 1
(bmp_header.compression == 0 || bmp_header.compression == 3))) { // No compression
return false;
}
switch (bmp_header.bpp) {
case 16:
file_pos = 0x36;
memset(buffer, 0, 16);
bmpimage.read(buffer, 16);
if (buffer[1] == 0x7C)
type = 3; // A1R5G5B5
else
type = 0; // R5G6B5
break;
case 24:
type = 1;
break;
case 32:
default:
type = 2;
break;
}
width = bmp_header.width;
height = bmp_header.height;
start_x = (screen_width - start_x - width) / 2 + start_x; // center horizontally
if (width > screen_width || width > 320)
return false;
file_pos = bmp_header.image_data;
py = height + 16 - 1;
/* ^ this is for to "start" AKA "image end" draw at the 17th line,
* because the render_line logic below is start with p.y() + py until "end" AKA "image start"*/
while (1) {
while (px < width) {
bmpimage.seek(file_pos);
memset(buffer, 0, 257);
read_size = bmpimage.read(buffer, 256);
if (read_size.is_error())
return false; // Read error
pointer = 0;
while (pointer < 256) {
if (pointer + 4 > 256)
break;
switch (type) {
case 0: // R5G6B5
case 3: // A1R5G5B5
if (!type)
line_buffer[px] = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8));
else
line_buffer[px] = ui::Color(((uint16_t)buffer[pointer] & 0x1F) | ((uint16_t)buffer[pointer] & 0xE0) << 1 | ((uint16_t)buffer[pointer + 1] & 0x7F) << 9);
pointer += 2;
file_pos += 2;
break;
case 1: // 24
default:
line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
pointer += 3;
file_pos += 3;
break;
case 2: // 32
line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
pointer += 4;
file_pos += 4;
break;
}
px++;
if (px >= width) {
break;
}
}
if (read_size.value() != 256)
break;
}
render_line({start_x, p.y() + py}, px, line_buffer);
px = 0;
py--;
if (read_size.value() < 256 || py < 0)
break;
}
return true;
}
void ILI9341::draw_line(const ui::Point start, const ui::Point end, const ui::Color color) {
int x0 = start.x();
int y0 = start.y();
int x1 = end.x();
int y1 = end.y();
int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2, e2;
for (;;) {
draw_pixel({static_cast<ui::Coord>(x0), static_cast<ui::Coord>(y0)}, color);
if (x0 == x1 && y0 == y1) break;
e2 = err;
if (e2 > -dx) {
err -= dy;
x0 += sx;
}
if (e2 < dy) {
err += dx;
y0 += sy;
}
}
}
void ILI9341::fill_circle(
const ui::Point center,
const ui::Dim radius,
const ui::Color foreground,
const ui::Color background) {
const uint32_t radius2 = radius * radius;
for (int32_t y = -radius; y < radius; y++) {
const int32_t y2 = y * y;
for (int32_t x = -radius; x < radius; x++) {
const int32_t x2 = x * x;
const uint32_t d2 = x2 + y2;
const bool inside = d2 < radius2;
const auto color = inside ? foreground : background;
draw_pixel({x + center.x(), y + center.y()}, color);
}
}
}
void ILI9341::draw_pixel(
const ui::Point p,
const ui::Color color) {
if (screen_rect().contains(p)) {
lcd_start_ram_write(p, {1, 1});
io.lcd_write_pixel(color);
}
}
void ILI9341::draw_pixels(
const ui::Rect r,
const ui::Color* const colors,
const size_t count) {
/* TODO: Assert that rectangle width x height < count */
lcd_start_ram_write(r);
io.lcd_write_pixels(colors, count);
}
void ILI9341::read_pixels(
const ui::Rect r,
ui::ColorRGB888* const colors,
const size_t count) {
/* TODO: Assert that rectangle width x height < count */
lcd_start_ram_read(r);
io.lcd_read_bytes(
reinterpret_cast<uint8_t*>(colors),
count * sizeof(ui::ColorRGB888));
}
void ILI9341::draw_bitmap(
const ui::Point p,
const ui::Size size,
const uint8_t* const pixels,
const ui::Color foreground,
const ui::Color background,
uint8_t zoom_level) {
if (zoom_level <= 1) {
// Not a transparent background
if (ui::Color::magenta().v != background.v) {
lcd_start_ram_write(p, size);
const size_t count = size.width() * size.height();
for (size_t i = 0; i < count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
io.lcd_write_pixel(pixel ? foreground : background);
}
} else {
// transparent bg
int x = p.x();
int y = p.y();
int maxX = x + size.width();
const size_t count = size.width() * size.height();
for (size_t i = 0; i < count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
if (pixel) {
if (x <= screen_width && y <= screen_height) draw_pixel(ui::Point(x, y), foreground);
}
// move to next px
x++;
if (x >= maxX) {
x = p.x();
y++;
}
}
}
} else { // zoom
// dot to square
for (int y = 0; y < size.height(); y++) {
for (int x = 0; x < size.width(); x++) {
// pos
size_t bit_index = y * size.width() + x;
int byte_index = bit_index >> 3;
int bit_pos = bit_index & 0x7;
// val
const auto pixel = pixels[byte_index] & (1U << bit_pos);
/* ^ the byte_index-th bit AKA current bit
^ current px in current byte*/
if (pixel || background.v != ui::Color::magenta().v) {
fill_rectangle(
{p.x() + x * zoom_level, p.y() + y * zoom_level,
zoom_level, zoom_level},
pixel ? foreground : background);
}
}
}
}
}
void ILI9341::draw_glyph(
const ui::Point p,
const ui::Glyph& glyph,
const ui::Color foreground,
const ui::Color background,
uint8_t zoom_level) {
draw_bitmap(p, glyph.size(), glyph.pixels(), foreground, background, zoom_level);
}
void ILI9341::scroll_set_area(
const ui::Coord top_y,
const ui::Coord bottom_y) {
scroll_state.top_area = top_y;
scroll_state.bottom_area = height() - bottom_y;
scroll_state.height = bottom_y - top_y;
lcd_vertical_scrolling_definition(scroll_state.top_area, scroll_state.height, scroll_state.bottom_area);
}
ui::Coord ILI9341::scroll_set_position(
const ui::Coord position) {
scroll_state.current_position = position % scroll_state.height;
const uint_fast16_t address = scroll_state.top_area + scroll_state.current_position;
lcd_vertical_scrolling_start_address(address);
return address;
}
ui::Coord ILI9341::scroll(const int32_t delta) {
return scroll_set_position(scroll_state.current_position + scroll_state.height - delta);
}
ui::Coord ILI9341::scroll_area_y(const ui::Coord y) const {
const auto wrapped_y = (scroll_state.current_position + y) % scroll_state.height;
return wrapped_y + scroll_state.top_area;
}
void ILI9341::scroll_disable() {
if (device_type == DEV_PORTAPACK) {
lcd_vertical_scrolling_definition(0, height(), 0);
lcd_vertical_scrolling_start_address(0);
} else {
io.lcd_data_write_command_and_data(0x13, {}); // normal mode
}
}
} /* namespace lcd */