#include "image_processor.h" #include #include void ImageProcessor::processAndPrint(const CameraService::Frame &frame, PrinterService &printer, const AppSettings &settings) { if (!frame.isValid()) return; // 1. Setup Dimensions (Rotate 90 degrees) // Camera is 320x240 (Landscape) -> Printer is 384 wide (Portrait) // We will map Camera Height (240) to Printer Width. // Since 240 < 384, the image will be centered or small. // If you want full width, we must scale, but rotation is cheaper. const int width = frame.getHeight(); // 240 const int height = frame.getWidth(); // 320 // 2. Allocate Buffer in PSRAM // We need int16_t to handle the overflow error during calculation size_t bufSize = width * height * sizeof(int16_t); int16_t *pixels = (int16_t *)heap_caps_malloc(bufSize, MALLOC_CAP_SPIRAM); if (!pixels) { Serial.println("[ERR] PSRAM Alloc Failed"); return; } // 3. Rotate & Load const uint8_t *src = frame.getData(); // Pre-calculate to save cycles in loop const int srcWidth = frame.getWidth(); for (int y = 0; y < srcWidth; y++) { // 320 (Source Y) for (int x = 0; x < width; x++) { // 240 (Source X) // 90 Deg Rotation Logic // Dest(x, y) = Src(y, srcWidth - x - 1) // We are mapping the camera buffer to our Dithering Buffer uint8_t val = src[x * srcWidth + y]; pixels[y * width + x] = static_cast(val); } } // 4. Floyd-Steinberg Dithering for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int16_t oldPixel = pixels[y * width + x]; int16_t newPixel = (oldPixel > 128) ? 255 : 0; pixels[y * width + x] = newPixel; int16_t error = oldPixel - newPixel; // Distribute Error (Bit shifting is faster than division) // Right: 7/16 if (x + 1 < width) pixels[y * width + (x + 1)] += (error * 7) >> 4; // Bottom-Left: 3/16 if (y + 1 < height) { if (x - 1 >= 0) pixels[(y + 1) * width + (x - 1)] += (error * 3) >> 4; // Bottom: 5/16 pixels[(y + 1) * width + x] += (error * 5) >> 4; // Bottom-Right: 1/16 if (x + 1 < width) pixels[(y + 1) * width + (x + 1)] += (error * 1) >> 4; } } } // 5. Pack & Print // We pack 8 pixels into 1 byte. // The Adafruit library expects a full bitmap array. // Optimization: We reuse the PSRAM allocation or alloc a smaller one. // Row stride must be multiple of 8 int rowBytes = (width + 7) / 8; size_t bitmapSize = rowBytes * height; uint8_t *bitmap = (uint8_t *)heap_caps_calloc(bitmapSize, 1, MALLOC_CAP_SPIRAM); if (!bitmap) { free(pixels); return; } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (pixels[y * width + x] == 0) { // If black bitmap[y * rowBytes + (x / 8)] |= (1 << (7 - (x % 8))); } } } printer.setHeat(settings.heatTime); printer.wake(); printer.printBitmap(width, height, bitmap); printer.feed(3); printer.sleep(); // 6. Cleanup (RAII handled elsewhere, but raw pointers need free) free(pixels); free(bitmap); } void ImageProcessor::uploadImage(const uint8_t *bitmap, int size, const String &url) { if (WiFi.status() != WL_CONNECTED) return; HTTPClient http; http.begin(url); http.addHeader("Content-Type", "application/octet-stream"); int responseCode = http.POST((uint8_t *)bitmap, size); if (responseCode > 0) { Serial.printf("[Upload] Success: %d\n", responseCode); } else { Serial.printf("[Upload] Failed: %s\n", http.errorToString(responseCode).c_str()); } http.end(); }