
Working code for images:
ESP32 (Arduino):
/*
* ESP32 Fast Image Display
* Receives multiple pixels per request
*/
#include <WiFi.h>
#include <WebServer.h>
#include <TFT_eSPI.h>
#define DISPLAY_ID 1
const char* ssid = "sweetpiWIFI";
const char* password = "ihopethisWORKS";
TFT_eSPI tft = TFT_eSPI();
WebServer server(80);
void setup() {
Serial.begin(115200);
// Init display
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
// Show startup
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
tft.setCursor(60, 140);
tft.print("Starting...");
// Connect WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
// Show ready
tft.fillScreen(TFT_BLACK);
tft.setCursor(80, 100);
tft.print("READY");
tft.setTextSize(1);
tft.setCursor(10, 200);
tft.print("IP: ");
tft.println(WiFi.localIP());
// Setup endpoints
server.on("/", handleRoot);
server.on("/test", handleTest);
server.on("/clear", handleClear);
server.on("/rect", handleRect); // Fast rectangle drawing
server.on("/row", HTTP_POST, handleRow); // Receive full row
server.begin();
}
void loop() {
server.handleClient();
}
void handleRoot() {
server.send(200, "text/plain", "OK");
}
void handleTest() {
// Fast test pattern using rectangles
tft.fillRect(0, 0, 80, 320, TFT_RED);
tft.fillRect(80, 0, 80, 320, TFT_GREEN);
tft.fillRect(160, 0, 80, 320, TFT_BLUE);
server.send(200, "text/plain", "OK");
}
void handleClear() {
tft.fillScreen(TFT_BLACK);
server.send(200, "text/plain", "OK");
}
void handleRect() {
// Draw filled rectangle (much faster than individual pixels)
if (server.hasArg("x") && server.hasArg("y") &&
server.hasArg("w") && server.hasArg("h") &&
server.hasArg("c")) {
int x = server.arg("x").toInt();
int y = server.arg("y").toInt();
int w = server.arg("w").toInt();
int h = server.arg("h").toInt();
uint16_t color = server.arg("c").toInt();
tft.fillRect(x, y, w, h, color);
}
server.send(200, "text/plain", "OK");
}
void handleRow() {
// Receive entire row of pixels at once
if (server.hasArg("y") && server.hasArg("plain")) {
int y = server.arg("y").toInt();
String data = server.arg("plain");
// Data format: pairs of bytes for RGB565 colors
int len = data.length();
for(int i = 0; i < len && i/2 < 240; i += 2) {
if(i+1 < len) {
uint16_t color = (data[i] << 8) | data[i+1];
tft.drawPixel(i/2, y, color);
}
}
}
server.send(200, "text/plain", "OK");
}
Raspberry Pi (Python)
cd ~/phone_controller
cat > fast_display.py << 'ENDOFFILE'
#!/usr/bin/env python3
"""
Fast Image Display for ESP32
Optimized for speed
"""
from flask import Flask, request, jsonify
from PIL import Image
import requests
import base64
import io
import time
import threading
from concurrent.futures import ThreadPoolExecutor
app = Flask(__name__)
ESP32_IP = "192.168.8.234"
def send_image_fast(image_data):
"""Send image using optimized methods"""
try:
# Decode image
if ',' in image_data:
image_data = image_data.split(',')[1]
image_bytes = base64.b64decode(image_data)
img = Image.open(io.BytesIO(image_bytes))
# Resize to fit display
img = img.resize((240, 320), Image.Resampling.LANCZOS)
# Convert to RGB
if img.mode != 'RGB':
img = img.convert('RGB')
# Clear display
requests.get(f"http://{ESP32_IP}/clear", timeout=1)
pixels = img.load()
print("Sending image fast...")
start_time = time.time()
# Method 1: Send rows (fastest)
for y in range(320):
row_data = bytearray()
for x in range(240):
r, g, b = pixels[x, y]
# Convert to RGB565
rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
row_data.append((rgb565 >> 8) & 0xFF)
row_data.append(rgb565 & 0xFF)
# Send entire row at once
try:
requests.post(
f"http://{ESP32_IP}/row",
data=bytes(row_data),
params={'y': y},
timeout=0.5
)
except:
pass
# Show progress every 40 rows
if y % 40 == 0:
print(f"Progress: {y}/320")
elapsed = time.time() - start_time
print(f"Image sent in {elapsed:.1f} seconds!")
return True
except Exception as e:
print(f"Error: {e}")
return False
def send_image_compressed(image_data):
"""Send image as compressed blocks"""
try:
# Decode image
if ',' in image_data:
image_data = image_data.split(',')[1]
image_bytes = base64.b64decode(image_data)
img = Image.open(io.BytesIO(image_bytes))
# Resize
img = img.resize((240, 320), Image.Resampling.LANCZOS)
# Reduce colors for faster transfer
img = img.convert('P', palette=Image.ADAPTIVE, colors=64)
img = img.convert('RGB')
# Clear display
requests.get(f"http://{ESP32_IP}/clear", timeout=1)
pixels = img.load()
print("Sending compressed image...")
# Find and send blocks of same color (RLE-like)
for y in range(0, 320, 4): # Process 4 rows at a time
for x in range(0, 240, 4): # Process 4 pixels at a time
# Get average color of 4x4 block
r_sum = g_sum = b_sum = 0
count = 0
for dy in range(4):
for dx in range(4):
if x+dx < 240 and y+dy < 320:
r, g, b = pixels[x+dx, y+dy]
r_sum += r
g_sum += g
b_sum += b
count += 1
if count > 0:
r_avg = r_sum // count
g_avg = g_sum // count
b_avg = b_sum // count
# Convert to RGB565
color = ((r_avg & 0xF8) << 8) | ((g_avg & 0xFC) << 3) | (b_avg >> 3)
# Send rectangle
try:
requests.get(
f"http://{ESP32_IP}/rect",
params={'x': x, 'y': y, 'w': 4, 'h': 4, 'c': color},
timeout=0.2
)
except:
pass
print("Compressed image sent!")
return True
except Exception as e:
print(f"Error: {e}")
return False
@app.route('/')
def index():
return '''
<!DOCTYPE html>
<html>
<head>
<title>Fast ESP32 Display</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 20px;
}
.container {
max-width: 400px;
margin: 0 auto;
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
}
h1 {
text-align: center;
font-size: 28px;
}
.btn {
width: 100%;
padding: 20px;
margin: 10px 0;
border: none;
border-radius: 15px;
font-size: 18px;
cursor: pointer;
color: white;
font-weight: bold;
transition: transform 0.2s;
}
.btn:active {
transform: scale(0.95);
}
.photo {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.fast {
background: linear-gradient(135deg, #11998e, #38ef7d);
}
.compressed {
background: linear-gradient(135deg, #f2994a, #f2c94c);
}
.test {
background: linear-gradient(135deg, #ee0979, #ff6a00);
}
.clear {
background: linear-gradient(135deg, #8e2de2, #4a00e0);
}
input {
display: none;
}
#preview {
max-width: 100%;
border-radius: 15px;
margin: 20px 0;
display: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
#status {
text-align: center;
padding: 15px;
background: rgba(255,255,255,0.2);
border-radius: 15px;
margin: 15px 0;
display: none;
font-size: 16px;
}
.info {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 15px;
margin: 15px 0;
text-align: center;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>⚡ Fast Display</h1>
<button class="btn photo" onclick="document.getElementById('file').click()">
📷 Select/Take Photo
<input type="file" id="file" accept="image/*">
</button>
<img id="preview">
<div id="status"></div>
<button class="btn fast" onclick="sendFast()">
🚀 Fast Send (~10 sec)
</button>
<button class="btn compressed" onclick="sendCompressed()">
⚡ Ultra Fast (~5 sec)
</button>
<div class="info">
Ultra Fast mode reduces quality slightly for 2x speed
</div>
<button class="btn test" onclick="test()">
🎨 Test Pattern
</button>
<button class="btn clear" onclick="clear()">
🔲 Clear
</button>
</div>
<script>
let imageData = null;
document.getElementById('file').onchange = function(e) {
const file = e.target.files[0];
if(file) {
const reader = new FileReader();
reader.onload = function(e) {
imageData = e.target.result;
document.getElementById('preview').src = imageData;
document.getElementById('preview').style.display = 'block';
showStatus('✅ Ready to send!');
};
reader.readAsDataURL(file);
}
};
function showStatus(msg) {
const status = document.getElementById('status');
status.textContent = msg;
status.style.display = 'block';
}
function sendFast() {
if(!imageData) {
showStatus('❌ Select an image first!');
return;
}
showStatus('⏳ Sending fast... (~10 seconds)');
fetch('/api/fast', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({image: imageData})
})
.then(r => r.json())
.then(data => {
showStatus(data.success ? '✅ Image displayed!' : '❌ Failed');
});
}
function sendCompressed() {
if(!imageData) {
showStatus('❌ Select an image first!');
return;
}
showStatus('⚡ Sending ultra fast... (~5 seconds)');
fetch('/api/compressed', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({image: imageData})
})
.then(r => r.json())
.then(data => {
showStatus(data.success ? '✅ Image displayed!' : '❌ Failed');
});
}
function test() {
fetch('/api/test', {method: 'POST'});
showStatus('🎨 Test pattern');
}
function clear() {
fetch('/api/clear', {method: 'POST'});
showStatus('🔲 Cleared');
}
</script>
</body>
</html>
'''
@app.route('/api/fast', methods=['POST'])
def fast():
data = request.get_json()
success = send_image_fast(data.get('image'))
return jsonify({'success': success})
@app.route('/api/compressed', methods=['POST'])
def compressed():
data = request.get_json()
success = send_image_compressed(data.get('image'))
return jsonify({'success': success})
@app.route('/api/test', methods=['POST'])
def test():
try:
requests.get(f"http://{ESP32_IP}/test", timeout=1)
return jsonify({'success': True})
except:
return jsonify({'success': False})
@app.route('/api/clear', methods=['POST'])
def clear():
try:
requests.get(f"http://{ESP32_IP}/clear", timeout=1)
return jsonify({'success': True})
except:
return jsonify({'success': False})
if __name__ == '__main__':
print("\n" + "="*40)
print("FAST ESP32 DISPLAY")
print("="*40)
print(f"ESP32 IP: {ESP32_IP}")
print("Access: http://192.168.8.241:5000")
print("\nSpeed options:")
print("- Fast mode: ~10 seconds (full quality)")
print("- Ultra fast: ~5 seconds (reduced quality)")
print("="*40 + "\n")
app.run(host='0.0.0.0', port=5000, debug=False)
ENDOFFILE
python3 fast_display.py
