mars91
Published © MIT

Pydafruit Gfx

pydafruit_gfx is a python tool to quickly prototype Adafruit GFX displays in python.

IntermediateProtip1 hour22
Pydafruit Gfx

Things used in this project

Hardware components

Adafruit Feather M0 Basic Proto - ATSAMD21 Cortex M0
Adafruit Feather M0 Basic Proto - ATSAMD21 Cortex M0
×1
Stacking Headers for Feather
×1
Adafruit FeatherWing OLED
×1

Story

Read more

Code

pyadafruit_gfx_demo.ino

C/C++
/*
  made by chatgpt, just pasted in my python code and said convert to arduino io file
  it seems a little funky but works
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/TomThumb.h>
#include <Fonts/Org_01.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

#define FPS 30
#define FRAME_MS (1000UL / FPS)
#define SPF (1.0f / FPS)

#define BLACK SH110X_BLACK
#define WHITE SH110X_WHITE

Adafruit_SH1107 display = Adafruit_SH1107(
  SCREEN_HEIGHT, SCREEN_WIDTH, &Wire, OLED_RESET
);

const uint8_t BMP_W = 32;
const uint8_t BMP_H = 32;

const uint8_t bitmap_python[] PROGMEM = {
  0x00,0x1F,0xF0,0x00,0x00,0x7F,0xFC,0x00,0x00,0x7F,0xFE,0x00,0x00,0xC7,0xFF,0x00,
  0x00,0xC7,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0xFF,0x00,
  0x0F,0xFF,0xFF,0x78,0x3F,0xFF,0xFF,0x7C,0x7F,0xFF,0xFF,0x7E,0x7F,0xFF,0xFF,0x7E,
  0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFC,0xFF,0xFF,0xF0,0x01,0xFF,
  0xFF,0x80,0x0F,0xFF,0xFF,0x3F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,
  0x7E,0xFF,0xFF,0xFE,0x7E,0xFF,0xFF,0xFE,0x3E,0xFF,0xFF,0xFC,0x1E,0xFF,0xFF,0xF0,
  0x00,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xE3,0x00,
  0x00,0xFF,0xE3,0x00,0x00,0x7F,0xFE,0x00,0x00,0x3F,0xFE,0x00,0x00,0x0F,0xF8,0x00,
};

const uint8_t bitmap_adafruit[] PROGMEM = {
  0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x00,0x00,0x01,0xE0,0x00,0x00,0x01,0xF0,0x00,
  0x00,0x03,0xF0,0x00,0x00,0x07,0xF0,0x00,0x00,0x07,0xF8,0x00,0x00,0x0F,0xF8,0x00,
  0x7F,0x0F,0xF8,0x00,0xFF,0xEF,0xF8,0x00,0xFF,0xFF,0xF8,0x00,0x7F,0xFE,0x7F,0xC0,
  0x3F,0xFE,0x7F,0xF8,0x1F,0xFE,0x7F,0xFF,0x1F,0xC6,0xFF,0xFF,0x0F,0xE3,0xC7,0xFE,
  0x07,0xFF,0x87,0xFC,0x01,0xFF,0xFF,0xF0,0x01,0xF3,0x7F,0xE0,0x03,0xE3,0x3F,0x80,
  0x07,0xE7,0x3C,0x00,0x07,0xFF,0xBE,0x00,0x07,0xFF,0xFE,0x00,0x0F,0xFF,0xFE,0x00,
  0x0F,0xFF,0xFF,0x00,0x0F,0xF9,0xFF,0x00,0x1F,0xF1,0xFF,0x00,0x1F,0x80,0xFF,0x00,
  0x1C,0x00,0x7F,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x06,0x00,
};

const uint8_t bitmap_invader[] PROGMEM = {
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0xC0,
  0x03,0xE0,0x07,0xC0,0x03,0xE0,0x07,0xC0,0x03,0xFC,0x3F,0xC0,0x03,0xFC,0x3F,0xC0,
  0x00,0x7C,0x3E,0x00,0x00,0x7C,0x3E,0x00,0x03,0xFF,0xFF,0xC0,0x03,0xFF,0xFF,0xC0,
  0x03,0xFF,0xFF,0xC0,0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x1F,0x83,0xC1,0xF8,
  0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,
  0xFF,0xFF,0xFF,0xFF,0xFB,0xFF,0xFF,0xDF,0xFB,0xFF,0xFF,0xDF,0xFB,0xFF,0xFF,0xDF,
  0xFB,0xE0,0x07,0xDF,0xFB,0xFE,0x7F,0xDF,0xFB,0xFE,0x7F,0xDF,0x00,0x7E,0x7E,0x00,
  0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

struct Star {
  float ox;
  float oy;
  float spd;
};

// These are generated from Python's random.seed(7), then 70 triples of:
// random.uniform(-1,1), random.uniform(-1,1), random.uniform(0.3,1.0)
const Star stars[70] PROGMEM = {
  {-0.352334470f, -0.698301652f, 0.755654131f},
  {-0.855127427f, 0.071764009f, 0.555982242f},
  {-0.884002150f, 0.014871466f, 0.326246961f},
  {-0.132708633f, -0.860289153f, 0.363499109f},
  {-0.150961622f, 0.653704249f, 0.386661373f},
  {-0.553522071f, 0.254866445f, 0.963396260f},
  {0.154205897f, -0.206639051f, 0.983378574f},
  {-0.906834639f, 0.716936918f, 0.502726500f},
  {-0.711489833f, -0.764415524f, 0.515937277f},
  {0.632252718f, -0.638547240f, 0.707120115f},
  {0.277826938f, -0.255204915f, 0.683421126f},
  {-0.874422050f, -0.880797660f, 0.444171099f},
  {0.360799946f, -0.144815389f, 0.519903019f},
  {0.171123727f, -0.093631247f, 0.509836898f},
  {0.588758963f, 0.397988867f, 0.470867558f},
  {0.148847421f, 0.050393008f, 0.912596247f},
  {0.458890579f, -0.424124470f, 0.986122393f},
  {-0.763868443f, -0.163754356f, 0.829998651f},
  {-0.696030931f, -0.022073799f, 0.327445080f},
  {0.336431713f, 0.529141732f, 0.701118158f},
  {0.750955624f, -0.372504974f, 0.786706756f},
  {0.188739754f, 0.159790409f, 0.619343732f},
  {0.679935561f, 0.889362190f, 0.631868836f},
  {0.328304411f, -0.878661145f, 0.791044415f},
  {0.294257709f, 0.986191879f, 0.875347351f},
  {-0.430808936f, -0.228417115f, 0.768056901f},
  {-0.954874144f, -0.076609427f, 0.417633865f},
  {-0.765808411f, -0.882091161f, 0.837763092f},
  {-0.741319556f, -0.504770333f, 0.573664792f},
  {0.742843948f, -0.838837398f, 0.614431181f},
  {0.098879818f, 0.766767653f, 0.873495886f},
  {0.727968939f, -0.443157871f, 0.590707562f},
  {-0.282457669f, 0.768385654f, 0.970411843f},
  {-0.698158188f, -0.647564543f, 0.462369807f},
  {-0.533327833f, -0.030074539f, 0.712386453f},
  {-0.474506761f, -0.991812793f, 0.593262551f},
  {-0.261492854f, 0.132682447f, 0.967168548f},
  {0.380987314f, 0.030982866f, 0.732314925f},
  {0.352400165f, -0.892014214f, 0.929673107f},
  {0.559938981f, 0.749026368f, 0.858511185f},
  {-0.215242186f, -0.202042335f, 0.372475966f},
  {0.268579131f, -0.875504357f, 0.347143331f},
  {-0.582473629f, -0.675393624f, 0.538037557f},
  {-0.894848792f, -0.999533436f, 0.405885453f},
  {-0.797071264f, -0.272780156f, 0.317850621f},
  {0.748664755f, 0.228137976f, 0.403985340f},
  {-0.495484487f, -0.305220908f, 0.554914408f},
  {-0.754315538f, 0.697873853f, 0.995171905f},
  {-0.068021082f, -0.032330687f, 0.360119263f},
  {-0.795624767f, -0.314728324f, 0.485329824f},
  {0.657710756f, -0.677122779f, 0.316167005f},
  {0.901971146f, 0.056514790f, 0.402621777f},
  {0.086344852f, -0.945915017f, 0.669676609f},
  {0.957002485f, 0.726650061f, 0.787337750f},
  {-0.477769606f, -0.266600416f, 0.416929424f},
  {0.543875817f, 0.065184795f, 0.845338424f},
  {-0.340670010f, -0.553916654f, 0.868057873f},
  {0.969852101f, 0.705257597f, 0.864255009f},
  {0.636665887f, 0.479746041f, 0.458717643f},
  {0.035277448f, -0.288874913f, 0.320286106f},
  {-0.944125849f, -0.441162922f, 0.481422054f},
  {0.385043883f, 0.913030153f, 0.613059374f},
  {0.874042403f, 0.976076116f, 0.968500442f},
  {-0.270728229f, -0.559075354f, 0.458792079f},
  {-0.606587673f, -0.591253273f, 0.736846478f},
  {0.800616676f, 0.680871055f, 0.635631398f},
  {0.305956086f, 0.599287490f, 0.359344941f},
  {0.321171300f, 0.819554275f, 0.847612019f},
  {0.500280920f, -0.043934511f, 0.424965203f},
  {0.578270862f, -0.334965600f, 0.860576498f},
};

enum Scene {
  SCENE_INSERT_COIN,
  SCENE_BOOT,
  SCENE_STARFIELD,
  SCENE_BOUNCE,
  SCENE_SPRITES,
  SCENE_CREDITS
};

Scene currentScene = SCENE_INSERT_COIN;
unsigned long sceneStartMs = 0;
unsigned long lastFrameMs = 0;
uint16_t frameNo = 0;

// Bounce scene state
float bounceX = SCREEN_WIDTH / 2.0f;
float bounceY = SCREEN_HEIGHT / 2.0f;
float bounceVX = 38.0f;
float bounceVY = 27.0f;
uint16_t bounceScore = 0;

const uint8_t TRAIL = 14;
int8_t trailX[TRAIL];
int8_t trailY[TRAIL];
uint8_t trailCount = 0;

// Sprite scene state
float spriteX[3];
uint8_t spriteLaps[3];

enum FontId {
  FONT_DEFAULT,
  FONT_FREE_SANS_BOLD_9,
  FONT_TOM_THUMB,
  FONT_ORG_01
};

struct CreditLine {
  const char *text;
  FontId font;
};

const CreditLine creditLines[] = {
  {"PYDAFRUIT", FONT_FREE_SANS_BOLD_9},
  {"", FONT_ORG_01},
  {"GFX", FONT_FREE_SANS_BOLD_9},
  {"", FONT_ORG_01},
  {"SDL2 backend", FONT_ORG_01},
  {"pybind11 bindings", FONT_ORG_01},
  {"Adafruit_GFX port", FONT_ORG_01},
  {"", FONT_ORG_01},
  {"draw_pixel", FONT_ORG_01},
  {"draw_line", FONT_ORG_01},
  {"fill_circle", FONT_ORG_01},
  {"draw_bitmap", FONT_ORG_01},
  {"set_font", FONT_ORG_01},
  {"get_text_bounds", FONT_ORG_01},
  {"", FONT_TOM_THUMB},
  {"github:pydafruitGFX", FONT_TOM_THUMB},
  {"MIT License", FONT_TOM_THUMB},
  {"", FONT_TOM_THUMB},
  {"* FIN *", FONT_FREE_SANS_BOLD_9},
  {"thanks!", FONT_TOM_THUMB},
};

const uint8_t CREDIT_COUNT = sizeof(creditLines) / sizeof(creditLines[0]);

void setFontId(FontId font) {
  switch (font) {
    case FONT_FREE_SANS_BOLD_9:
      display.setFont(&FreeSansBold9pt7b);
      break;
    case FONT_TOM_THUMB:
      display.setFont(&TomThumb);
      break;
    case FONT_ORG_01:
      display.setFont(&Org_01);
      break;
    case FONT_DEFAULT:
    default:
      display.setFont();
      break;
  }
}

int16_t textWidth(const char *text, int16_t x = 0, int16_t y = 0) {
  int16_t x1, y1;
  uint16_t w, h;
  display.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  return (int16_t)w;
}

void printCentered(const char *text, int16_t baselineY) {
  int16_t tw = textWidth(text, 0, baselineY);
  display.setCursor((SCREEN_WIDTH - tw) / 2, baselineY);
  display.print(text);
}

float sceneSeconds() {
  return (millis() - sceneStartMs) / 1000.0f;
}

void transitionWipe() {
  int16_t cx = SCREEN_WIDTH / 2;

  for (int16_t i = 0; i < 80; i += 2) {
    display.drawCircle(cx, SCREEN_HEIGHT, i, BLACK);
    display.drawCircle(cx - 1, SCREEN_HEIGHT, i, BLACK);
    display.drawCircle(cx, SCREEN_HEIGHT - 1, i * 2, BLACK);
    display.drawCircle(cx, SCREEN_HEIGHT - 2, i * 3, BLACK);
    display.display();
    delay(10);
  }

  display.clearDisplay();
  display.display();
}

void resetSceneState(Scene scene) {
  sceneStartMs = millis();
  frameNo = 0;

  if (scene == SCENE_BOUNCE) {
    bounceX = SCREEN_WIDTH / 2.0f;
    bounceY = SCREEN_HEIGHT / 2.0f;
    bounceVX = 38.0f;
    bounceVY = 27.0f;
    bounceScore = 0;
    trailCount = 0;
  }

  if (scene == SCENE_SPRITES) {
    spriteX[0] = -BMP_W - 20.0f;
    spriteX[1] = -BMP_W - 60.0f;
    spriteX[2] = -BMP_W - 40.0f;
    spriteLaps[0] = 0;
    spriteLaps[1] = 0;
    spriteLaps[2] = 0;
  }
}

void nextScene() {
  currentScene = (Scene)((currentScene + 1) % 6);
  resetSceneState(currentScene);
}

void drawInsertCoin() {
  display.clearDisplay();

  setFontId(FONT_FREE_SANS_BOLD_9);
  display.setTextColor(WHITE);
  display.setTextSize(1);

  printCentered("INSERT", 15);
  printCentered("COIN", 32);

  setFontId(FONT_TOM_THUMB);
  printCentered("press Q to skip", 53);

  uint8_t slotW = frameNo % 8;
  int16_t slotX = SCREEN_WIDTH / 2 - slotW / 2;

  if (slotW > 0) {
    display.fillRoundRect(slotX, 36, slotW, 10, 2, WHITE);
  }

  if (((frameNo / 30) % 2 == 0) && frameNo > 60) {
    setFontId(FONT_TOM_THUMB);
    printCentered("-- INSERT COIN --", 62);
  }

  display.display();

  if (millis() - sceneStartMs >= 4000UL) {
    transitionWipe();
    nextScene();
  }
}

void drawBoot() {
  float t = sceneSeconds();

  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);

  if (t > 0.4f) {
    display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, WHITE);
    display.drawRect(2, 2, SCREEN_WIDTH - 4, SCREEN_HEIGHT - 4, WHITE);
  }

  if (t > 0.8f) {
    setFontId(FONT_FREE_SANS_BOLD_9);
    display.setCursor(6, 19);
    display.print("PYDAFRUIT");
    display.setCursor(30, 35);
    display.print("GFX");
  }

  if (t > 1.4f) {
    setFontId(FONT_TOM_THUMB);
    display.setCursor(8, 48);
    display.print("SDL2+pybind11");
  }

  if (t > 2.0f && ((int)(t * 3.0f) % 2 == 0)) {
    setFontId(FONT_TOM_THUMB);
    display.setCursor(8, 58);
    display.print("< DEMO >");
  }

  display.display();

  if (millis() - sceneStartMs >= 3500UL) {
    nextScene();
  }
}

void drawStarfield() {
  float t = sceneSeconds();
  int16_t cx = SCREEN_WIDTH / 2;
  int16_t cy = SCREEN_HEIGHT / 2;

  display.clearDisplay();

  float speed = min(t / 1.5f, 1.0f) * 4.0f + 0.4f;

  for (uint8_t i = 0; i < 70; i++) {
    Star s;
    memcpy_P(&s, &stars[i], sizeof(Star));

    float z = fmod(t * s.spd * speed, 2.0f);
    float sc = z / 2.0f;

    int16_t x = (int16_t)(cx + s.ox * sc * SCREEN_WIDTH);
    int16_t y = (int16_t)(cy + s.oy * sc * SCREEN_HEIGHT);

    if (x < 0 || x >= SCREEN_WIDTH || y < 0 || y >= SCREEN_HEIGHT) {
      continue;
    }

    if (sc > 0.5f) {
      int16_t px = (int16_t)(cx + s.ox * max(0.0f, sc - 0.18f) * SCREEN_WIDTH);
      int16_t py = (int16_t)(cy + s.oy * max(0.0f, sc - 0.18f) * SCREEN_HEIGHT);
      display.drawLine(px, py, x, y, WHITE);
    } else {
      display.drawPixel(x, y, WHITE);
    }
  }

  setFontId(FONT_TOM_THUMB);
  display.setTextColor(WHITE);
  display.setCursor(2, SCREEN_HEIGHT - 1);
  display.print("STARFIELD");

  display.display();

  if (millis() - sceneStartMs >= 5000UL) {
    nextScene();
  }
}

void pushTrail(int16_t x, int16_t y) {
  if (trailCount < TRAIL) {
    trailX[trailCount] = (int8_t)x;
    trailY[trailCount] = (int8_t)y;
    trailCount++;
  } else {
    for (uint8_t i = 1; i < TRAIL; i++) {
      trailX[i - 1] = trailX[i];
      trailY[i - 1] = trailY[i];
    }
    trailX[TRAIL - 1] = (int8_t)x;
    trailY[TRAIL - 1] = (int8_t)y;
  }
}

void drawBounce() {
  const int16_t R = 5;

  bounceX += bounceVX * SPF;
  bounceY += bounceVY * SPF;

  bool bounced = false;

  if (bounceX - R < 0) {
    bounceX = R;
    bounceVX = abs(bounceVX);
    bounced = true;
  }

  if (bounceX + R >= SCREEN_WIDTH) {
    bounceX = SCREEN_WIDTH - R - 1;
    bounceVX = -abs(bounceVX);
    bounced = true;
  }

  if (bounceY - R < 0) {
    bounceY = R;
    bounceVY = abs(bounceVY);
    bounced = true;
  }

  if (bounceY + R >= SCREEN_HEIGHT) {
    bounceY = SCREEN_HEIGHT - R - 1;
    bounceVY = -abs(bounceVY);
    bounced = true;
  }

  if (bounced) {
    bounceScore++;
  }

  pushTrail((int16_t)bounceX, (int16_t)bounceY);

  display.clearDisplay();
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, WHITE);

  for (uint8_t i = 0; i + 1 < trailCount; i++) {
    if (i % 2 == 0) {
      display.drawPixel(trailX[i], trailY[i], WHITE);
    }
  }

  display.fillCircle((int16_t)bounceX, (int16_t)bounceY, R, WHITE);
  display.drawCircle((int16_t)bounceX, (int16_t)bounceY, R + 2, WHITE);

  setFontId(FONT_TOM_THUMB);
  display.setTextColor(WHITE);
  display.setCursor(3, 7);
  display.print("SCORE ");
  if (bounceScore < 100) display.print("0");
  if (bounceScore < 10) display.print("0");
  display.print(bounceScore);

  display.display();

  if (millis() - sceneStartMs >= 6000UL) {
    nextScene();
  }
}

void drawSprites() {
  const int16_t laneY[3] = {
    0,
    SCREEN_HEIGHT / 2 - BMP_H / 2,
    SCREEN_HEIGHT - BMP_H
  };

  const float speeds[3] = {20.0f, 34.0f, 19.0f};

  display.clearDisplay();

  for (int16_t x = 0; x < SCREEN_WIDTH; x += 6) {
    display.drawPixel(x, laneY[1] - 1, WHITE);
    display.drawPixel(x, laneY[2] - 1, WHITE);
    display.drawPixel(x, SCREEN_HEIGHT - BMP_H / 2, WHITE);
  }

  display.drawFastVLine(SCREEN_WIDTH - 2, 0, SCREEN_HEIGHT, WHITE);

  for (uint8_t i = 0; i < 3; i++) {
    spriteX[i] += speeds[i] * SPF;

    if (spriteX[i] > SCREEN_WIDTH + 4) {
      spriteX[i] = -BMP_W;
      spriteLaps[i]++;
    }

    int16_t ix = (int16_t)spriteX[i];
    int16_t iy = laneY[i];

    if (ix > -BMP_W && ix < SCREEN_WIDTH) {
      if (i == 0) {
        display.drawBitmap(ix, iy, bitmap_python, BMP_W, BMP_H, WHITE);
      } else if (i == 1) {
        display.drawBitmap(ix, iy, bitmap_adafruit, BMP_W, BMP_H, WHITE);
      } else {
        display.drawBitmap(ix, iy, bitmap_invader, BMP_W, BMP_H, WHITE);
      }
    }

    setFontId(FONT_TOM_THUMB);
    display.setTextColor(WHITE);
    display.setCursor(SCREEN_WIDTH - 18, iy + BMP_H - 2);
    display.print("x");
    display.print(spriteLaps[i]);
  }

  setFontId(FONT_TOM_THUMB);
  display.setTextColor(WHITE);
  display.setCursor(2, 5);
  display.print("SPRITE RACE");

  display.display();

  if (millis() - sceneStartMs >= 7000UL) {
    nextScene();
  }
}

void drawCredits() {
  const float SCROLL_SPEED = 18.0f;
  const int16_t LINE_H = 12;
  float t = sceneSeconds();
  int16_t scroll = (int16_t)(t * SCROLL_SPEED);

  display.clearDisplay();

  for (uint8_t i = 0; i < CREDIT_COUNT; i++) {
    int16_t y = SCREEN_HEIGHT - scroll + i * LINE_H + LINE_H;

    if (y < -LINE_H || y > SCREEN_HEIGHT + LINE_H) {
      continue;
    }

    setFontId(creditLines[i].font);
    display.setTextColor(WHITE);
    printCentered(creditLines[i].text, y);
  }

  display.fillRect(0, 0, SCREEN_WIDTH, 4, BLACK);
  display.fillRect(0, SCREEN_HEIGHT - 4, SCREEN_WIDTH, 4, BLACK);

  display.display();

  unsigned long durationMs = (unsigned long)(((CREDIT_COUNT * LINE_H + SCREEN_HEIGHT) / SCROLL_SPEED + 1.0f) * 1000.0f);

  if (millis() - sceneStartMs >= durationMs) {
    nextScene();
  }
}

void setup() {
  Wire.begin();

  if (!display.begin(0x3C, true)) {
    while (1) {
      delay(10);
    }
  }

  display.clearDisplay();
  display.display();
  display.setRotation(1);

  setFontId(FONT_DEFAULT);
  display.setTextColor(WHITE);
  display.setTextSize(1);

  resetSceneState(currentScene);
  lastFrameMs = millis();
}

void loop() {
  unsigned long now = millis();

  if (now - lastFrameMs < FRAME_MS) {
    return;
  }

  lastFrameMs = now;
  frameNo++;

  switch (currentScene) {
    case SCENE_INSERT_COIN:
      drawInsertCoin();
      break;

    case SCENE_BOOT:
      drawBoot();
      break;

    case SCENE_STARFIELD:
      drawStarfield();
      break;

    case SCENE_BOUNCE:
      drawBounce();
      break;

    case SCENE_SPRITES:
      drawSprites();
      break;

    case SCENE_CREDITS:
      drawCredits();
      break;
  }
}

pydafruit_instructables_demo.py

Python
import pydafruit_gfx as pyfx
import math, time, random

## OLED 
## --------------------------------------------------------------
W, H  = 128, 64
BLACK = 0x0000
WHITE = 0xFFFF

## SDL2 
## --------------------------------------------------------------
SCALE = 6
FPS   = 30
SPF   = 1.0 / FPS

disp = pyfx.GFXDisplay(W, H, scale=SCALE)
if not disp.begin("pydafruit_gfx  Retro OLED"):
    raise RuntimeError("Failed to open SDL2 window")

def transition_wipe():
    ## https://hackaday.io/project/203611-ssid-silly-space-invaders-dashboard
    for i in range(0, 80, 2):
        if not disp.is_open():
            return False
        disp.handle_events()
        cx = W // 2
        disp.draw_circle(cx,     H,     i,     BLACK)
        disp.draw_circle(cx - 1, H,     i,     BLACK)
        disp.draw_circle(cx,     H - 1, i * 2, BLACK)
        disp.draw_circle(cx,     H - 2, i * 3, BLACK)
        disp.flush()
        time.sleep(0.01)
    disp.fill_screen(BLACK)
    disp.flush()
    return True

def run_scene(draw_fn, duration):
    t0 = time.monotonic()
    frame = 0
    while disp.is_open():
        t = time.monotonic() - t0
        if t >= duration:
            return True
        ft = time.monotonic()
        if not disp.handle_events():
            return False
        draw_fn(t, frame)
        disp.flush()
        sleep = SPF - (time.monotonic() - ft)
        if sleep > 0:
            time.sleep(sleep)
        frame += 1
    return False


## Coin Spin 
## --------------------------------------------------------------
def scene_insert_coin():
    TOTAL = 256
    frames_done = [0]

    def draw(t, frame):
        w_val = frame % 256
        disp.fill_screen(BLACK)

        disp.set_font("FreeSansBold9")
        disp.set_text_color(WHITE)

        disp.set_text_size(1)
        title1 = "INSERT"
        _, _, tw, th = disp.get_text_bounds(title1, 0, 0)
        disp.set_cursor((W - tw) // 2, 15)
        disp.print(title1)

        title2 = "COIN"
        _, _, tw, th = disp.get_text_bounds(title2, 0, 0)
        disp.set_cursor((W - tw) // 2, 32)
        disp.print(title2)


        disp.set_font("TomThumb")
        disp.set_text_color(WHITE)

        title3 = "press Q to skip"
        _, _, tw, th = disp.get_text_bounds(title3, 0, 0)
        disp.set_cursor((W - tw) // 2, 53)
        disp.print(title3)

        slot_w = w_val % 8
        slot_x = W//2 - slot_w // 2
        if slot_w > 0:
            disp.fill_round_rect(slot_x, 36, slot_w, 10, 2, WHITE)

        if (frame // 30) % 2 == 0 and frame > 60:
            disp.set_font("TomThumb")
            disp.set_text_color(WHITE)
            title4 = "-- INSERT COIN --"
            _, _, tw, th = disp.get_text_bounds(title4, 0, 0)
            disp.set_cursor((W - tw) // 2, 62)
            disp.print(title4)

        frames_done[0] = frame

    ok = run_scene(draw, 4.0)
    if ok:
        transition_wipe()
    return ok


## Intro
## --------------------------------------------------------------
def scene_boot():
    def draw(t, frame):
        disp.fill_screen(BLACK)

        if t > 0.4:
            disp.draw_rect(0, 0, W, H, WHITE)
            disp.draw_rect(2, 2, W-4, H-4, WHITE)

        if t > 0.8:
            disp.set_font("FreeSansBold9")
            disp.set_text_color(WHITE)
            disp.set_cursor(6, 19)
            disp.print("PYDAFRUIT")
            disp.set_cursor(30, 35)
            disp.print("GFX")

        if t > 1.4:
            disp.set_font("TomThumb")
            # disp.set_font("FreeMono9")
            disp.set_text_color(WHITE)
            disp.set_cursor(8, 48)
            disp.print("SDL2+pybind11")

        if t > 2.0 and int(t * 3) % 2 == 0:
            disp.set_font("TomThumb")
            disp.set_text_color(WHITE)
            disp.set_cursor(8, 58)
            disp.print("< DEMO >")

    return run_scene(draw, 3.5)

## star field
## --------------------------------------------------------------
def scene_starfield():
    random.seed(7)
    N = 70
    stars = [(random.uniform(-1,1), random.uniform(-1,1),
              random.uniform(0.3, 1.0)) for _ in range(N)]
    cx, cy = W//2, H//2

    def draw(t, frame):
        disp.fill_screen(BLACK)
        speed = min(t / 1.5, 1.0) * 4.0 + 0.4

        for ox, oy, spd in stars:
            z  = ((t * spd * speed) % 2.0)
            sc = z / 2.0
            x  = int(cx + ox * sc * W)
            y  = int(cy + oy * sc * H)
            if not (0 <= x < W and 0 <= y < H):
                continue
            if sc > 0.5:
                px = int(cx + ox * max(0, sc - 0.18) * W)
                py = int(cy + oy * max(0, sc - 0.18) * H)
                disp.draw_line(px, py, x, y, WHITE)
            else:
                disp.draw_pixel(x, y, WHITE)

        disp.set_font("TomThumb")
        disp.set_text_color(WHITE)
        disp.set_cursor(2, H-1)
        disp.print("STARFIELD")

    return run_scene(draw, 5.0)

## ball bounce
## --------------------------------------------------------------
def scene_bounce():
    R     = 5
    bx    = float(W//2)
    by    = float(H//2)
    vx    = 38.0
    vy    = 27.0
    score = 0
    trail = []
    TRAIL = 14

    def draw(t, frame):
        nonlocal bx, by, vx, vy, score

        bx += vx * SPF
        by += vy * SPF

        bounced = False
        if bx - R < 0:     bx = float(R);     vx =  abs(vx); bounced=True
        if bx + R >= W:    bx = float(W-R-1); vx = -abs(vx); bounced=True
        if by - R < 0:     by = float(R);     vy =  abs(vy); bounced=True
        if by + R >= H:    by = float(H-R-1); vy = -abs(vy); bounced=True
        if bounced:
            score += 1

        trail.append((int(bx), int(by)))
        if len(trail) > TRAIL:
            trail.pop(0)

        disp.fill_screen(BLACK)
        disp.draw_rect(0, 0, W, H, WHITE)

        for i, (tx, ty) in enumerate(trail[:-1]):
            if i % 2 == 0:
                disp.draw_pixel(tx, ty, WHITE)

        disp.fill_circle(int(bx), int(by), R, WHITE)
        disp.draw_circle(int(bx), int(by), R+2, WHITE)

        disp.set_font("TomThumb")
        disp.set_text_color(WHITE)
        disp.set_cursor(3, 7)
        disp.print(f"SCORE {score:03d}")

    return run_scene(draw, 6.0)

## sprite race
## --------------------------------------------------------------
BMP_W = BMP_H = 32

bitmap_python = bytes([
  0x00,0x1F,0xF0,0x00,0x00,0x7F,0xFC,0x00,0x00,0x7F,0xFE,0x00,0x00,0xC7,0xFF,0x00,
  0x00,0xC7,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0xFF,0x00,
  0x0F,0xFF,0xFF,0x78,0x3F,0xFF,0xFF,0x7C,0x7F,0xFF,0xFF,0x7E,0x7F,0xFF,0xFF,0x7E,
  0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFC,0xFF,0xFF,0xF0,0x01,0xFF,
  0xFF,0x80,0x0F,0xFF,0xFF,0x3F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,
  0x7E,0xFF,0xFF,0xFE,0x7E,0xFF,0xFF,0xFE,0x3E,0xFF,0xFF,0xFC,0x1E,0xFF,0xFF,0xF0,
  0x00,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xE3,0x00,
  0x00,0xFF,0xE3,0x00,0x00,0x7F,0xFE,0x00,0x00,0x3F,0xFE,0x00,0x00,0x0F,0xF8,0x00,
])
bitmap_adafruit = bytes([
  0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x00,0x00,0x01,0xE0,0x00,0x00,0x01,0xF0,0x00,
  0x00,0x03,0xF0,0x00,0x00,0x07,0xF0,0x00,0x00,0x07,0xF8,0x00,0x00,0x0F,0xF8,0x00,
  0x7F,0x0F,0xF8,0x00,0xFF,0xEF,0xF8,0x00,0xFF,0xFF,0xF8,0x00,0x7F,0xFE,0x7F,0xC0,
  0x3F,0xFE,0x7F,0xF8,0x1F,0xFE,0x7F,0xFF,0x1F,0xC6,0xFF,0xFF,0x0F,0xE3,0xC7,0xFE,
  0x07,0xFF,0x87,0xFC,0x01,0xFF,0xFF,0xF0,0x01,0xF3,0x7F,0xE0,0x03,0xE3,0x3F,0x80,
  0x07,0xE7,0x3C,0x00,0x07,0xFF,0xBE,0x00,0x07,0xFF,0xFE,0x00,0x0F,0xFF,0xFE,0x00,
  0x0F,0xFF,0xFF,0x00,0x0F,0xF9,0xFF,0x00,0x1F,0xF1,0xFF,0x00,0x1F,0x80,0xFF,0x00,
  0x1C,0x00,0x7F,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x06,0x00,
])
bitmap_invader = bytes([
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0xC0,
  0x03,0xE0,0x07,0xC0,0x03,0xE0,0x07,0xC0,0x03,0xFC,0x3F,0xC0,0x03,0xFC,0x3F,0xC0,
  0x00,0x7C,0x3E,0x00,0x00,0x7C,0x3E,0x00,0x03,0xFF,0xFF,0xC0,0x03,0xFF,0xFF,0xC0,
  0x03,0xFF,0xFF,0xC0,0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x1F,0x83,0xC1,0xF8,
  0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,0xFF,0x83,0xC1,0xFF,
  0xFF,0xFF,0xFF,0xFF,0xFB,0xFF,0xFF,0xDF,0xFB,0xFF,0xFF,0xDF,0xFB,0xFF,0xFF,0xDF,
  0xFB,0xE0,0x07,0xDF,0xFB,0xFE,0x7F,0xDF,0xFB,0xFE,0x7F,0xDF,0x00,0x7E,0x7E,0x00,
  0x00,0x7E,0x7E,0x00,0x00,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
])

def scene_sprites():
    LANE_Y = [0, H//2 - BMP_H//2, H - BMP_H]
    SPEEDS = [20.0, 34.0, 19.0]
    BMPS   = [bitmap_python, bitmap_adafruit, bitmap_invader]

    xs    = [float(-BMP_W - 20), float(-BMP_W - 60), float(-BMP_W - 40)]
    laps  = [0, 0, 0]

    def draw(t, frame):
        disp.fill_screen(BLACK)

        for x in range(0, W, 6):
            disp.draw_pixel(x, LANE_Y[1] - 1, WHITE)
            disp.draw_pixel(x, LANE_Y[2] - 1, WHITE)
            disp.draw_pixel(x, H - BMP_H//2, WHITE)

        disp.draw_fast_vline(W-2, 0, H, WHITE)

        for i in range(3):
            xs[i] += SPEEDS[i] * SPF
            if xs[i] > W + 4:
                xs[i] = float(-BMP_W)
                laps[i] += 1

            ix = int(xs[i])
            iy = LANE_Y[i]
            if -BMP_W < ix < W:
                disp.draw_bitmap(ix, iy, BMPS[i], BMP_W, BMP_H, WHITE)

            disp.set_font("TomThumb")
            disp.set_text_color(WHITE)
            disp.set_cursor(W - 18, iy + BMP_H - 2)
            disp.print(f"x{laps[i]}")

        disp.set_font("TomThumb")
        disp.set_text_color(WHITE)
        disp.set_cursor(2, 5)
        disp.print("SPRITE RACE")

    return run_scene(draw, 7.0)

## star wars credits
## --------------------------------------------------------------
def scene_credits():
    lines = [
        ("PYDAFRUIT",    "FreeSansBold9"),
        ("",                  "Org01"),
        ("GFX",          "FreeSansBold9"),
        ("",                  "Org01"),
        ("SDL2 backend",      "Org01"),
        ("pybind11 bindings", "Org01"),
        ("Adafruit_GFX port", "Org01"),
        ("",                  "Org01"),
        ("draw_pixel",        "Org01"),
        ("draw_line",         "Org01"),
        ("fill_circle",       "Org01"),
        ("draw_bitmap",       "Org01"),
        ("set_font",          "Org01"),
        ("get_text_bounds",   "Org01"),
        ("",                  "TomThumb"),
        ("github:pydafruitGFX",      "TomThumb"),
        ("MIT License",       "TomThumb"),
        ("",                  "TomThumb"),
        ("* FIN *",           "FreeSansBold9"),
        ("thanks!",      "TomThumb"),
    ]

    SCROLL_SPEED = 18.0
    LINE_H       = 12

    def draw(t, frame):
        disp.fill_screen(BLACK)
        scroll = t * SCROLL_SPEED

        for i, (text, font) in enumerate(lines):
            y = H - int(scroll) + i * LINE_H + LINE_H
            if y < -LINE_H or y > H + LINE_H:
                continue
            disp.set_font(font)
            _, _, tw, _ = disp.get_text_bounds(text, 0, y)
            disp.set_text_color(WHITE)
            disp.set_cursor((W - tw) // 2, y)
            disp.print(text)

        disp.fill_rect(0, 0,   W, 4, BLACK)
        disp.fill_rect(0, H-4, W, 4, BLACK)

    duration = (len(lines) * LINE_H + H) / SCROLL_SPEED + 1.0
    return run_scene(draw, duration)

## main loop
## --------------------------------------------------------------
scenes = [
    scene_insert_coin,
    scene_boot, 
    scene_starfield, 
    scene_bounce, 
    scene_sprites, 
    scene_credits]

running = True
while running:
    for fn in scenes:
        if not disp.is_open():
            running = False
            break
        if not fn():
            running = False
            break

pydafruitGFX

Credits

mars91
2 projects • 3 followers

Comments