commit fd4b2047ae73e7df28da51c727ecb6a9ba84501f Author: jimmy Date: Sun Jun 4 18:38:15 2023 +1200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c02241 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.grb +*.png +.venv +.vscode \ No newline at end of file diff --git a/editor.py b/editor.py new file mode 100755 index 0000000..270e75d --- /dev/null +++ b/editor.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 + +import pygame +from pygame import Color, Rect +from pygame.locals import * +import datetime +import array + +# Initialize pygame +pygame.init() + +# Set up the display +pixel_size = 40 +width = 16 * pixel_size +height = 16 * pixel_size + 100 # Increased height for the color palette +screen = pygame.display.set_mode((width, height)) + +# Create a 16x16 pixel array to store the colors +pixels = [[(0, 0, 0) for _ in range(16)] for _ in range(16)] + +# Create a stack to store previous pixel states for undo +undo_stack = [] + +# Set up the drawing flag +drawing = False + +# Color palette (24-bit RGB colors) +palette = [ + Color(0, 0, 0), # Black + Color(255, 255, 255), # White + Color(255, 0, 0), # Red + Color(0, 255, 0), # Green + Color(0, 0, 255), # Blue + Color(255, 255, 0), # Yellow + Color(255, 0, 255), # Magenta + Color(0, 255, 255) # Cyan +] + +# Set up the color palette dimensions +palette_x = 20 +palette_y = height - 80 +palette_width = width - 40 +palette_height = 60 +color_width = palette_width // len(palette) + +# Set up the initial selected color +selected_color = palette[0] + +# Main loop +running = True +while running: + for event in pygame.event.get(): + if event.type == QUIT: + running = False + elif event.type == MOUSEBUTTONDOWN: + if event.button == 1: # Left mouse button + x, y = event.pos + # Check if the click is inside the color palette + if palette_y <= y < palette_y + palette_height: + color_index = (x - palette_x) // color_width + # Update the selected color for drawing + if 0 <= color_index < len(palette): + selected_color = palette[color_index] + else: + row = (y - 20) // pixel_size + col = x // pixel_size + if 0 <= row < 16 and 0 <= col < 16: + # Store the current pixel state for undo + undo_stack.append([row, col, pixels[row][col]]) + drawing = True + pixels[row][col] = selected_color + elif event.type == MOUSEBUTTONUP: + if event.button == 1: # Left mouse button + drawing = False + elif event.type == MOUSEMOTION: + if drawing: + x, y = event.pos + row = (y - 20) // pixel_size + col = x // pixel_size + if 0 <= row < 16 and 0 <= col < 16: + # Store the current pixel state for undo + undo_stack.append([row, col, pixels[row][col]]) + pixels[row][col] = selected_color + elif event.type == KEYDOWN: + if event.key == K_s and pygame.key.get_mods() & KMOD_CTRL: + # Create a new surface with the pixel art + pixel_art = pygame.Surface((width, height)) + for row in range(16): + for col in range(16): + pygame.draw.rect(pixel_art, pixels[row][col], Rect(col * pixel_size, row * pixel_size + 20, pixel_size, pixel_size)) + + # Save the pixel art as an image + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"art.png" + pygame.image.save(pixel_art, filename, "PNG") + print(f"Pixel art saved as {filename}!") + + # Convert RGB values to binary format + bitstream = array.array("B") + for y in range(16): + if y % 2 == 0: # Every second line (odd-indexed line) + pixels_row = pixels[y][::-1] # Flip the line + else: + pixels_row = pixels[y] + + # Iterate over the RGB values of the line + for pixel in pixels_row: + r, g, b, *_ = pixel # Unpack the first three values and ignore the rest + + # Apply gamma correction if needed (optional) + # r = int(pow(r / 255, 2.8) * 255) + # g = int(pow(g / 255, 2.8) * 255) + # b = int(pow(b / 255, 2.8) * 255) + + # Write the color components to the binary array in GRB order for WS2812 + bitstream.extend([g, r, b]) + + # Save the binary data as a file + filename = f"art.grb" + with open(filename, "wb") as file: + file.write(bitstream) + + print(f"Pixel art saved as {filename}!") + + if event.key == K_u and pygame.key.get_mods() & KMOD_CTRL: + # Undo the previous modification if available + if undo_stack: + row, col, color = undo_stack.pop() + pixels[row][col] = color + print("Undo") + + # Clear the screen + screen.fill((255, 255, 255)) + + # Draw the outline of the canvas + pygame.draw.rect(screen, (0, 0, 0), Rect(0, 20, width, height - 100), 1) + + # Draw the color palette + for i, color in enumerate(palette): + pygame.draw.rect(screen, color, Rect(palette_x + i * color_width, palette_y, color_width, palette_height)) + + # Draw the pixels on the screen + for row in range(16): + for col in range(16): + pygame.draw.rect(screen, pixels[row][col], Rect(col * pixel_size, row * pixel_size + 20, pixel_size, pixel_size)) + + pygame.display.update() + +# Quit the program +pygame.quit() diff --git a/pico.py b/pico.py new file mode 100644 index 0000000..27fff48 --- /dev/null +++ b/pico.py @@ -0,0 +1,52 @@ +import machine +import sdcard +import uos +import array, time +from machine import Pin +import rp2 +from ws2812b import WS2812B + +NUM_LEDS = 256 + +ws = WS2812B(256, 28, 0.1) + +# Assign chip select (CS) pin (and start it high) +cs = machine.Pin(13, machine.Pin.OUT) + +# Intialize SPI peripheral (start with 1 MHz) +spi = machine.SPI(1, + baudrate=1000000, + polarity=0, + phase=0, + bits=8, + firstbit=machine.SPI.MSB, + sck=machine.Pin(10), + mosi=machine.Pin(11), + miso=machine.Pin(12)) + +# Initialize SD card +sd = sdcard.SDCard(spi, cs) + +# Mount filesystem +vfs = uos.VfsFat(sd) +uos.mount(vfs, "/sd") +print(uos.listdir("/sd")) + + +with open("/sd/art.grb", 'rb') as f: + while True: + frame = f.read(256*3) + if not frame: + print("End of frames") + break + + for i in range(256): + offset = i * 3 + green, red, blue = frame[offset:offset + 3] + print(red, green, blue) + ws.pixels_set(i, (red, green, blue)) + ws.pixels_show() + + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ee4e712 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pygame +pillow \ No newline at end of file diff --git a/ws2812b.py b/ws2812b.py new file mode 100644 index 0000000..b97a670 --- /dev/null +++ b/ws2812b.py @@ -0,0 +1,108 @@ +# Example using PIO to drive a set of WS2812 LEDs. + +import array, time +from machine import Pin +import rp2 +from time import sleep + +# Configure the number of WS2812 LEDs. +NUM_LEDS = 256 +PIN_NUM = 0 +brightness = 0.2 + +@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) +def ws2812(): + T1 = 2 + T2 = 5 + T3 = 3 + wrap_target() + label("bitloop") + out(x, 1) .side(0) [T3 - 1] + jmp(not_x, "do_zero") .side(1) [T1 - 1] + jmp("bitloop") .side(1) [T2 - 1] + label("do_zero") + nop() .side(0) [T2 - 1] + wrap() + +class WS2812B: + def __init__(self, num_leds, pin_num=0, brightness=1, sm=0): + # Create the StateMachine with the ws2812 program, outputting on pin + self.sm = rp2.StateMachine(sm, ws2812, freq=8_000_000, sideset_base=Pin(pin_num)) + + # Start the StateMachine, it will wait for data on its FIFO. + self.sm.active(1) + + # Display a pattern on the LEDs via an array of LED RGB values. + self.ar = array.array("I", [0 for _ in range(num_leds)]) + self.num_leds = num_leds + self.brightness = brightness + + ########################################################################## + def pixels_show(self): + dimmer_ar = array.array("I", [0 for _ in range(self.num_leds)]) + for i,c in enumerate(self.ar): + r = int(((c >> 8) & 0xFF) * self.brightness) + g = int(((c >> 16) & 0xFF) * self.brightness) + b = int((c & 0xFF) * self.brightness) + dimmer_ar[i] = (g<<16) + (r<<8) + b + self.sm.put(dimmer_ar, 8) + time.sleep_ms(1) + + def pixels_set(self, i, color): + self.ar[i] = (color[1]<<16) + (color[0]<<8) + color[2] + + def pixels_fill(self, color): + for i in range(len(self.ar)): + self.pixels_set(i, color) + + def color_chase(self, color, wait): + for i in range(self.num_leds): + self.pixels_set(i, color) + time.sleep(wait) + self.pixels_show() + time.sleep(0.2) + + def wheel(self, pos): + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if pos < 0 or pos > 255: + return (0, 0, 0) + if pos < 85: + return (255 - pos * 3, pos * 3, 0) + if pos < 170: + pos -= 85 + return (0, 255 - pos * 3, pos * 3) + pos -= 170 + return (pos * 3, 0, 255 - pos * 3) + + + def rainbow_cycle(self, wait): + for j in range(255): + for i in range(self.num_leds): + rc_index = (i * 256 // self.num_leds) + j + self.pixels_set(i, self.wheel(rc_index & 255)) + self.pixels_show() + time.sleep(wait) + + BLACK = (0, 0, 0) + RED = (255, 0, 0) + YELLOW = (255, 150, 0) + GREEN = (0, 255, 0) + CYAN = (0, 255, 255) + BLUE = (0, 0, 255) + PURPLE = (180, 0, 255) + WHITE = (255, 255, 255) + COLORS = (BLACK, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE) + + + +if __name__ == "__main__": + ws = WS2812B(10, 28, 0.1, 7) + ws2 = WS2812B(10) + while True: + for color in ws.COLORS: + ws.pixels_fill(color) + ws.pixels_show() + ws2.pixels_fill(color) + ws2.pixels_show() + time.sleep(0.2)