Files
party-cam/src/image_processor.cpp
2026-01-12 00:53:54 +01:00

137 lines
4.0 KiB
C++

#include "image_processor.h"
#include <Arduino.h>
#include <HTTPClient.h>
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<int16_t>(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();
}