Ankur Majumdar
Published © MIT

Sharingan Mask: Awakening in the Dark

A light-reactive anime mask that awakens 8+ Sharingan styles in darkness, built from junk parts using a single ESP32.

IntermediateFull instructions provided3 hours19
Sharingan Mask: Awakening in the Dark

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×1
GC9A01 - Round Displays 1.28
×2
Jumper wires (generic)
Jumper wires (generic)
×16
PHPoC Bread Board
PHPoC Bread Board
×1
LDR, 5 Mohm
LDR, 5 Mohm
×1

Software apps and online services

Arduino IDE
Arduino IDE
File to C Style Array
Ez Gif

Story

Read more

Schematics

Circuit Diagram to Connect Two Displays with ESP-32

Optional Circuit Diagram to Connect LDR with ESP-32

Sharingan Eyes - 8 Sharingan Transition Header File.

Sharingan Eyes - 2 Sharingan Transition Header File

Code

Sharingan Eyes Code

C/C++
// Animated GIF with Round Display (mirrored to 2 screens)
//
// ESP32 40MHz SPI single frame rendering performance

#include <SPI.h>
#include <TFT_eSPI.h>     // Install this library with the Arduino IDE Library Manager
                          // Don't forget to configure the driver for the display!
#include <AnimatedGIF.h>  // Install this library with the Arduino IDE Library Manager
AnimatedGIF gif;

// Examples images
#include "images/hyperspace.h"
#include "images/nostromo.h"
#include "images/hud_1.h"
#include "images/hud_2.h"
#include "images/hud_5.h"
#include "images/hud_6.h"
#include "images/hud_7.h"
#include "images/darthvader.h"
#include "images/x_wing.h"
#include "images/colortest.h"
#include "images/animated.h"
#include "images/Sharingan.h"
#include "images/Sharinganmain.h"
// Uncomment the image to display
//#define GIF_IMAGE colortest
//#define GIF_IMAGE hyperspace
// #define GIF_IMAGE nostromo
//#define GIF_IMAGE darthvader
//#define GIF_IMAGE hud_1
// #define GIF_IMAGE hud_2
//#define GIF_IMAGE hud_5
//#define GIF_IMAGE hud_6
// #define GIF_IMAGE hud_7
//#define GIF_IMAGE Animated_GIFs_davidope_11
//#define GIF_IMAGE x_wing
#define GIF_IMAGE Sharingan
//#define GIF_IMAGE sharingan_2



// ---- Only additions: 2 CS pins ----
const int CS1 = 14;
const int CS2 = 15;

TFT_eSPI tft = TFT_eSPI();

void setup() {
  Serial.begin(115200);

  pinMode(CS1, OUTPUT);
  pinMode(CS2, OUTPUT);

  // Deselect both
  digitalWrite(CS1, HIGH);
  digitalWrite(CS2, HIGH);

  // Select both for init so both get initialized together [web:4]
  digitalWrite(CS1, LOW);
  digitalWrite(CS2, LOW);

  tft.begin();
  tft.setRotation(2);     // Adjust Rotation of your screen (0-3)
  tft.fillScreen(TFT_BLACK);

  // Deselect both
  digitalWrite(CS1, HIGH);
  digitalWrite(CS2, HIGH);

  gif.begin(BIG_ENDIAN_PIXELS);
}

void loop()
{
  if (gif.open((uint8_t *)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw))
  {
    Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n",
                  gif.getCanvasWidth(), gif.getCanvasHeight());

    // Select both so all rendering goes to both displays (mirrored) [web:4]
    digitalWrite(CS1, LOW);
    digitalWrite(CS2, LOW);

    tft.startWrite();
    while (gif.playFrame(true, NULL))
    {
      yield();
    }
    gif.close();
    tft.endWrite();

    // Deselect both
    digitalWrite(CS1, HIGH);
    digitalWrite(CS2, HIGH);
  }
}

Basic ino file to draw on the TFT_espi displays

C/C++
// GIFDraw is called by AnimatedGIF library frame to screen

#define DISPLAY_WIDTH  tft.width()
#define DISPLAY_HEIGHT tft.height()
#define BUFFER_SIZE 256            // Optimum is >= GIF width or integral division of width

#ifdef USE_DMA
  uint16_t usTemp[2][BUFFER_SIZE]; // Global to support DMA use
#else
  uint16_t usTemp[1][BUFFER_SIZE];    // Global to support DMA use
#endif
bool     dmaBuf = 0;
  
// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{
  uint8_t *s;
  uint16_t *d, *usPalette;
  int x, y, iWidth, iCount;

  // Displ;ay bounds chech and cropping
  iWidth = pDraw->iWidth;
  if (iWidth + pDraw->iX > DISPLAY_WIDTH)
    iWidth = DISPLAY_WIDTH - pDraw->iX;
  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line
  if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1)
    return;

  // Old image disposal
  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) // restore to background color
  {
    for (x = 0; x < iWidth; x++)
    {
      if (s[x] == pDraw->ucTransparent)
        s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }

  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) // if transparency used
  {
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    pEnd = s + iWidth;
    x = 0;
    iCount = 0; // count non-transparent pixels
    while (x < iWidth)
    {
      c = ucTransparent - 1;
      d = &usTemp[0][0];
      while (c != ucTransparent && s < pEnd && iCount < BUFFER_SIZE )
      {
        c = *s++;
        if (c == ucTransparent) // done, stop
        {
          s--; // back up to treat it like transparent
        }
        else // opaque
        {
          *d++ = usPalette[c];
          iCount++;
        }
      } // while looking for opaque pixels
      if (iCount) // any opaque pixels?
      {
        // DMA would degrtade performance here due to short line segments
        tft.setAddrWindow(pDraw->iX + x, y, iCount, 1);
        tft.pushPixels(usTemp, iCount);
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent)
          x++;
        else
          s--;
      }
    }
  }
  else
  {
    s = pDraw->pPixels;

    // Unroll the first pass to boost DMA performance
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    if (iWidth <= BUFFER_SIZE)
      for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];
    else
      for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];

#ifdef USE_DMA // 71.6 fps (ST7796 84.5 fps)
    tft.dmaWait();
    tft.setAddrWindow(pDraw->iX, y, iWidth, 1);
    tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount);
    dmaBuf = !dmaBuf;
#else // 57.0 fps
    tft.setAddrWindow(pDraw->iX, y, iWidth, 1);
    tft.pushPixels(&usTemp[0][0], iCount);
#endif

    iWidth -= iCount;
    // Loop if pixel buffer smaller than width
    while (iWidth > 0)
    {
      // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
      if (iWidth <= BUFFER_SIZE)
        for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];
      else
        for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++];

#ifdef USE_DMA
      tft.dmaWait();
      tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount);
      dmaBuf = !dmaBuf;
#else
      tft.pushPixels(&usTemp[0][0], iCount);
#endif
      iWidth -= iCount;
    }
  }
} /* GIFDraw() */

Default Human Eyes Code

C/C++
No preview (download only).

Credits

Ankur Majumdar
2 projects • 3 followers
Passionate about AI at the edge, embedded systems, and building meaningful hardware solutions that connect intelligence with emotion.

Comments