palette
authorFrank DeMarco <if.self.end@gmail.com>
Sun, 17 Feb 2019 23:29:18 +0000 (18:29 -0500)
committerFrank DeMarco <if.self.end@gmail.com>
Sun, 17 Feb 2019 23:29:18 +0000 (18:29 -0500)
PictureProcessing.py

index cab00b9..f75ec1b 100644 (file)
@@ -109,21 +109,29 @@ class Editor(GameChild):
 
     TILE_SIZE = 8
     PIXEL_ZOOM = 32
-    CURSOR_THICKNESS = 3
-    UI_EDITOR_HEADING = "EDITOR"
-    UI_EXIT = "ESC: EXIT", "B: EXIT"
-    UI_EDIT = "ENTER: EDIT", "A: EDIT"
-    UI_TILES = "\\24 TILES"
-    UI_PLAY = "TEST \\26"
-    UI_PAINT = "\\25 PAINT"
-    UI_MUSIC = "\\27 MUSIC"
-    UI_SAVE_EXIT = "ESC: SAVE", "B: SAVE"
-    UI_HIDE_MENU = "ENTER: HIDE", "A: HIDE"
-    UI_HEADING_MARGIN = 60
-    UI_BUTTONS_MARGIN = 60
-    UI_VIEW_MARGIN = 120
-    UI_DIRECTIONS_MARGIN = 0
-    UI_ALIGN_ONE_THIRD_BOTTOM, UI_ALIGN_TWO_THIRDS_BOTTOM = range(2)
+    CANVAS_SCALE = 40
+    LABEL_EDITOR_HEADING = "EDITOR"
+    LABEL_EXIT = "ESC: EXIT", "B: EXIT"
+    LABEL_EDIT = "ENTER: EDIT", "A: EDIT"
+    LABEL_TILES = "\\24 TILES"
+    LABEL_PLAY = "TEST \\26"
+    LABEL_PAINT_ARROW = "\\25 PAINT"
+    LABEL_MUSIC = "\\27 MUSIC"
+    LABEL_SAVE_EXIT = "ESC: SAVE", "B: SAVE"
+    LABEL_HIDE_MENU = "ENTER: HIDE", "A: HIDE"
+    LABEL_BACK = "ESC: BACK", "B: BACK"
+    LABEL_PAINT = "Z: PAINT", "A: PAINT"
+    LABEL_NEW = "ENTER: NEW", "A: NEW"
+    LABEL_SELECT = "ENTER: SELECT", "A: SELECT"
+    HEADING_MARGIN = 60
+    BUTTONS_MARGIN = 60
+    VIEW_MARGIN = 120
+    DIRECTIONS_MARGIN = 0
+    ALIGN_ONE_THIRD_BOTTOM, ALIGN_TWO_THIRDS_BOTTOM = range(2)
+    TILE_BAR_PADDING = 10
+    TILE_BAR_BACKGROUND = 0, 0, 0
+    TILE_BAR_SCALE = 3
+    PALETTE_WIDTH = 100
 
     def __init__(self, parent):
         GameChild.__init__(self, parent)
@@ -134,7 +142,13 @@ class Editor(GameChild):
         for _ in xrange(self.TILE_SIZE):
             self.painting.append([0] * self.TILE_SIZE)
         self.at_level_select = True
+        self.at_view = False
+        self.at_tile_bar = False
+        self.at_tile_edit = False
+        self.at_palette = False
         self.level_index = 0
+        self.tile_index = 0
+        self.palette_index = 0
         self.loaded = False
         self.menu_hidden = False
 
@@ -150,32 +164,77 @@ class Editor(GameChild):
         self.read_levels()
         glyphs = self.get_game().glyphs
         dsr = self.get_display_surface().get_rect()
-        self.editor_heading = self.get_label(self.UI_EDITOR_HEADING, True)
-        self.editor_heading.location.midtop = dsr.centerx, self.UI_HEADING_MARGIN
+        self.editor_heading = self.get_label(self.LABEL_EDITOR_HEADING, True)
+        self.editor_heading.location.midtop = dsr.centerx, self.HEADING_MARGIN
         self.arrows = Sprite(self), Sprite(self)
         self.background = ScrollingBackground(self, self.levels[-1].tiles)
         self.level_select_exit = self.get_label(
-            self.UI_EXIT, align=self.UI_ALIGN_ONE_THIRD_BOTTOM,
-            margin=self.UI_BUTTONS_MARGIN)
-        self.level_select_edit = self.get_label(
-            self.UI_EDIT, align=self.UI_ALIGN_TWO_THIRDS_BOTTOM,
-            margin=self.UI_BUTTONS_MARGIN)
+            self.LABEL_EXIT, align=self.ALIGN_ONE_THIRD_BOTTOM,
+            margin=self.BUTTONS_MARGIN)
+        self.label_edit_level = self.get_label(
+            self.LABEL_EDIT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.BUTTONS_MARGIN)
         self.view_exit = self.get_label(
-            self.UI_SAVE_EXIT, align=self.UI_ALIGN_ONE_THIRD_BOTTOM,
-            margin=self.UI_VIEW_MARGIN)
+            self.LABEL_SAVE_EXIT, align=self.ALIGN_ONE_THIRD_BOTTOM,
+            margin=self.VIEW_MARGIN)
         self.view_hide = self.get_label(
-            self.UI_HIDE_MENU, align=self.UI_ALIGN_TWO_THIRDS_BOTTOM,
-            margin=self.UI_VIEW_MARGIN)
-        self.arrow_tiles = self.get_label(self.UI_TILES)
+            self.LABEL_HIDE_MENU, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_back = self.get_label(
+            self.LABEL_BACK, align=self.ALIGN_ONE_THIRD_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_paint = self.get_label(
+            self.LABEL_PAINT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_edit_view = self.get_label(
+            self.LABEL_EDIT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_new = self.get_label(
+            self.LABEL_NEW, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_select = self.get_label(
+            self.LABEL_SELECT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.arrow_tiles = self.get_label(self.LABEL_TILES)
         self.arrow_tiles.location.midtop = dsr.midtop
-        self.arrow_play = self.get_label(self.UI_PLAY)
+        self.arrow_play = self.get_label(self.LABEL_PLAY)
         self.arrow_play.location.midright = dsr.midright
-        self.arrow_paint = self.get_label(self.UI_PAINT)
+        self.arrow_paint = self.get_label(self.LABEL_PAINT_ARROW)
         self.arrow_paint.location.midbottom = dsr.midbottom
-        self.arrow_music = self.get_label(self.UI_MUSIC)
+        self.arrow_music = self.get_label(self.LABEL_MUSIC)
         self.arrow_music.location.midleft = dsr.midleft
+        self.tile_bar = Sprite(self)
+        bar_tile_height = self.get_current_level().original_tiles[0].get_width() * \
+            self.TILE_BAR_SCALE
+        frame = Surface((dsr.w, bar_tile_height + self.TILE_BAR_PADDING * 2))
+        frame.fill(self.TILE_BAR_BACKGROUND)
+        self.tile_bar.add_frame(frame)
+        self.level_select_cursor = self.get_cursor(
+            *self.get_current_level().preview.get_size())
+        self.level_select_cursor.location.center = dsr.center
+        self.tile_cursor = self.get_cursor(
+            *([self.TILE_SIZE * self.TILE_BAR_SCALE] * 2))
+        self.paint_cursor = self.get_cursor(*([self.CANVAS_SCALE] * 2))
+        self.palette_cursor = self.get_cursor()
+        new_tile = self.new_tile = Surface([self.TILE_SIZE] * 2)
+        new_tile.fill((255, 255, 255))
+        for x in xrange(new_tile.get_width()):
+            for y in xrange(new_tile.get_height()):
+                if 3 <= x <= 4 and y != 0 and y != 7:
+                    new_tile.set_at((x, y), (0, 0, 0))
+                elif 3 <= y <= 4 and x != 0 and x != 7:
+                    new_tile.set_at((x, y), (0, 0, 0))
+        self.build_default_palette()
         self.loaded = True
 
+    def build_default_palette(self):
+        self.default_palette = [Color(255, 255, 255), Color(0, 0, 0)]
+        for hue in xrange(0, 360 - 45, 45):
+            for lightness in xrange(25, 100, 25):
+                color = Color(0, 0, 0)
+                color.hsla = hue, 100, lightness, 100
+                self.default_palette.append(color)
+
     def get_label(self, text, rainbow=False, align=None, margin=0):
         if isinstance(text, tuple):
             text = self.get_game().select_text(text)
@@ -188,9 +247,9 @@ class Editor(GameChild):
             label.add_frame(surface)
         if align is not None:
             dsr = self.get_display_surface().get_rect()
-            if align == self.UI_ALIGN_ONE_THIRD_BOTTOM:
+            if align == self.ALIGN_ONE_THIRD_BOTTOM:
                 label.location.midbottom = dsr.centerx / 2, dsr.h - margin
-            elif align == self.UI_ALIGN_TWO_THIRDS_BOTTOM:
+            elif align == self.ALIGN_TWO_THIRDS_BOTTOM:
                 label.location.midbottom = \
                     dsr.centerx / 2 + dsr.centerx, dsr.h - margin
         return label
@@ -237,36 +296,129 @@ class Editor(GameChild):
                     self.menu_hidden = False
                 elif self.at_view:
                     self.menu_hidden = not self.menu_hidden
+                elif self.at_tile_bar:
+                    if self.add_tile_selected():
+                        tile = Surface([self.TILE_SIZE] * 2)
+                        tile.fill((0, 0, 0))
+                        self.get_current_level().add_tile(tile)
+                    self.at_tile_edit = True
+                    self.at_tile_bar = False
+                elif self.at_palette:
+                    self.at_palette = False
+                    self.at_tile_edit = True
             if compare(event, "cancel"):
                 if self.at_level_select:
                     self.deactivate()
                 elif self.at_view:
                     self.at_view = False
                     self.at_level_select = True
+                elif self.at_tile_bar:
+                    self.at_tile_bar = False
+                    self.at_view = True
+                elif self.at_tile_edit:
+                    self.at_tile_edit = False
+                    self.at_tile_bar = True
+                elif self.at_palette:
+                    self.at_palette = False
+                    self.at_tile_edit = True
             if compare(event, "right"):
-                dx = 1
+                if self.at_tile_bar:
+                    self.tile_index += 1
+                    if self.tile_index == Interface.MAX_TILE_COUNT or \
+                       self.tile_index > len(self.get_current_level().tiles):
+                        self.tile_index = 0
+                if self.at_tile_edit:
+                    if self.brush_position.x == self.TILE_SIZE - 1:
+                        self.at_tile_edit = False
+                        self.at_palette = True
+                        if self.palette_index_is_at_left():
+                            self.wrap_palette_index()
+                    else:
+                        dx = 1
+                elif self.at_palette:
+                    if self.palette_index_is_at_left():
+                        self.at_palette = False
+                        self.at_tile_edit = True
+                    else:
+                        self.wrap_palette_index()
             elif compare(event, "down"):
-                dy = 1
+                if self.at_tile_edit:
+                    dy = 1
+                if self.at_palette:
+                    self.palette_index += 1
             elif compare(event, "left"):
-                dx = -1
+                if self.at_tile_bar:
+                    self.tile_index -= 1
+                    if self.tile_index < 0:
+                        self.tile_index = min(
+                            Interface.MAX_TILE_COUNT - 1,
+                            len(self.get_current_level().tiles))
+                if self.at_tile_edit:
+                    if self.brush_position.x == 0:
+                        self.at_tile_edit = False
+                        self.at_palette = True
+                        if not self.palette_index_is_at_left():
+                            self.wrap_palette_index()
+                    else:
+                        dx = -1
+                elif self.at_palette:
+                    if not self.palette_index_is_at_left():
+                        self.at_palette = False
+                        self.at_tile_edit = True
+                    else:
+                        self.wrap_palette_index()
             elif compare(event, "up"):
-                dy = -1
+                if self.at_view:
+                    self.at_view = False
+                    self.at_tile_bar = True
+                if self.at_tile_edit:
+                    dy = -1
+                if self.at_palette:
+                    self.palette_index -= 1
             elif compare(event, "paint"):
                 self.painting[self.brush_position.x][self.brush_position.y] = \
                     not self.painting[self.brush_position.x][self.brush_position.y]
             if dx != 0 or dy != 0:
-                self.move_cursor(dx, dy)
+                self.move_brush_position(dx, dy)
+            if self.palette_index >= self.get_palette_cell_count():
+                self.palette_index = 0
+            elif self.palette_index < 0:
+                self.palette_index = self.get_palette_cell_count() - 1
+
+    def wrap_palette_index(self):
+        count = self.get_palette_cell_count()
+        if count % 2 and self.palette_index == count / 2:
+            self.palette_index = count - 1
+        else:
+            self.palette_index += [-1, 1][self.palette_index_is_at_left()] * \
+                (count / 2 + count % 2)
 
-    def move_cursor(self, dx, dy):
+    def get_palette_cell_count(self):
+        return len(self.get_full_palette()) + 1
+
+    def palette_index_is_at_left(self):
+        length = self.get_palette_cell_count()
+        return self.palette_index < length / 2 + length % 2
+
+    def move_brush_position(self, dx, dy):
         self.brush_position.move(dx, dy)
         if self.brush_position.x >= self.TILE_SIZE:
             self.brush_position.x = self.TILE_SIZE - 1
         elif self.brush_position.x < 0:
             self.brush_position.x = 0
         if self.brush_position.y >= self.TILE_SIZE:
-            self.brush_position.y = self.TILE_SIZE - 1
-        elif self.brush_position.y < 0:
             self.brush_position.y = 0
+        elif self.brush_position.y < 0:
+            self.brush_position.y = self.TILE_SIZE - 1
+
+    def get_current_level(self):
+        return self.levels[self.level_index]
+
+    def get_cursor(self, width=0, height=0, flashing=True):
+        return FlashingCursor(self, width, height, flashing)
+
+    def add_tile_selected(self):
+        return self.tile_index == len(self.get_current_level().original_tiles)
 
     def update(self):
         if self.active:
@@ -276,13 +428,15 @@ class Editor(GameChild):
                 self.background.update()
                 self.editor_heading.update()
                 self.level_select_exit.update()
-                self.level_select_edit.update()
-                preview = self.levels[self.level_index].preview
+                self.label_edit_level.update()
+                preview = self.get_current_level().preview
                 rect = preview.get_rect()
                 rect.center = ds.get_rect().center
                 ds.blit(preview, rect)
+                self.level_select_cursor.update()
             else:
-                self.levels[self.level_index].update()
+                self.get_current_level().update()
+            if self.at_view:
                 if not self.menu_hidden:
                     self.view_exit.update()
                     self.view_hide.update()
@@ -290,18 +444,152 @@ class Editor(GameChild):
                     self.arrow_play.update()
                     self.arrow_paint.update()
                     self.arrow_music.update()
-                pz = self.PIXEL_ZOOM
-                for x, row in enumerate(self.painting):
-                    for y, col in enumerate(row):
-                        if col == 0:
-                            color = 0, 0, 0
+            if self.at_tile_bar or self.at_tile_edit or self.at_palette:
+                self.tile_bar.update()
+                tiles = self.get_current_level().original_tiles
+                if len(tiles) < Interface.MAX_TILE_COUNT:
+                    full_tiles = tiles + [self.new_tile]
+                else:
+                    full_tiles = tiles
+                for ii, tile in enumerate(full_tiles):
+                    cell_width = self.tile_bar.location.w / Interface.MAX_TILE_COUNT
+                    scaled_tile = scale(
+                        tile, [tiles[0].get_width() * self.TILE_BAR_SCALE] * 2)
+                    rect = scaled_tile.get_rect()
+                    rect.center = cell_width * ii + cell_width / 2, \
+                        self.tile_bar.location.centery
+                    ds.blit(scaled_tile, rect)
+                    if ii == self.tile_index:
+                        self.tile_cursor.location.center = rect.center
+                        if self.at_tile_bar:
+                            self.tile_cursor.set_flashing(True)
+                            self.tile_cursor.update()
+                        else:
+                            self.tile_cursor.set_flashing(False)
+                self.label_back.update()
+                if not self.add_tile_selected():
+                    tile = tiles[self.tile_index]
+                    canvas = scale(tile, [self.CANVAS_SCALE * tile.get_width()] * 2)
+                    rect = canvas.get_rect()
+                    rect.center = ds.get_rect().center
+                    ds.blit(canvas, rect)
+                    palette = self.get_full_palette()
+                    length = len(palette) + 1
+                    color_left_height = canvas.get_height() / (length / 2 + length % 2)
+                    color_right_height = canvas.get_height() / (length / 2)
+                    palette_left_height = color_left_height * (length / 2 + length % 2)
+                    palette_right_height = color_right_height * (length / 2)
+                    palette_left_rect = Rect(0, 0, self.PALETTE_WIDTH, palette_left_height)
+                    palette_left_rect.midright = rect.left - 30, rect.centery
+                    palette_right_rect = Rect(0, 0, self.PALETTE_WIDTH, palette_right_height)
+                    palette_right_rect.midleft = rect.right + 30, rect.centery
+                    y = palette_left_rect.top
+                    for ii in xrange(length):
+                        if ii < length / 2 + length % 2:
+                            palette_rect = palette_left_rect
+                            h = color_left_height
                         else:
-                            color = 255, 255, 255
-                        ds.fill(color, (x * pz, y * pz, pz, pz))
-                bx, by = self.brush_position
-                cr = bx * pz, by * pz, pz, pz
-                draw.rect(ds, (255, 255, 255), cr, self.CURSOR_THICKNESS)
-                draw.rect(ds, (0, 0, 0), cr, 1)
+                            palette_rect = palette_right_rect
+                            h = color_left_height
+                        if ii < length - 1:
+                            ds.fill(palette[ii], (palette_rect.left, y, palette_rect.w, h))
+                        else:
+                            for x in xrange(palette_rect.w):
+                                hue = int(float(x) / palette_rect.w * 360)
+                                color = Color(0, 0, 0)
+                                color.hsla = hue, 100, 50, 100
+                                ds.fill(color, (palette_rect.left + x, y, 1, h))
+                        if ii == (length / 2 + length % 2) - 1:
+                            y = palette_right_rect.top
+                        else:
+                            y += color_left_height
+                    if self.at_tile_bar:
+                        self.label_edit_view.update()
+                elif self.at_tile_bar:
+                    self.label_new.update()
+                if self.at_tile_edit or self.at_palette:
+                    if self.at_tile_edit:
+                        paint_flashing = True
+                        palette_flashing = False
+                    else:
+                        paint_flashing = False
+                        palette_flashing = True
+                    self.paint_cursor.set_flashing(paint_flashing)
+                    self.paint_cursor.set_size(*([self.CANVAS_SCALE] * 2))
+                    self.palette_cursor.set_flashing(palette_flashing)
+                    self.palette_cursor.set_size(palette_rect.w, color_left_height)
+                    self.paint_cursor.location.topleft = \
+                        rect.left + self.brush_position.x * self.CANVAS_SCALE, \
+                        rect.top + self.brush_position.y * self.CANVAS_SCALE
+                    self.paint_cursor.update()
+                    if self.at_tile_edit:
+                        self.label_paint.update()
+                    elif self.at_palette:
+                        self.label_select.update()
+                    if self.palette_index < length / 2 + length % 2:
+                        left = palette_left_rect.left
+                        top = color_left_height * self.palette_index
+                    else:
+                        left = palette_right_rect.left
+                        top = color_left_height * (self.palette_index - (length / 2 + length % 2))
+                    self.palette_cursor.location.topleft = left - 1, palette_rect.top + top - 1
+                    self.palette_cursor.update()
+
+    def get_full_palette(self):
+        return self.default_palette + self.get_current_colors()
+
+    def get_current_colors(self):
+        colors = []
+        if not self.add_tile_selected():
+            tile = self.get_current_level().original_tiles[self.tile_index]
+            for x in xrange(0, tile.get_width()):
+                for y in xrange(0, tile.get_height()):
+                    color = tile.get_at((x, y))
+                    if color not in colors and color not in self.default_palette:
+                        colors.append(color)
+        return colors
+
+
+class FlashingCursor(Sprite):
+
+    THICKNESS = 3
+
+    def __init__(self, parent, width=0, height=0, flashing=True):
+        Sprite.__init__(self, parent)
+        self.set_flashing(flashing)
+        self.set_size(width, height)
+        self.hue = 0
+        self.set_frame()
+
+    def set_flashing(self, state):
+        self.flashing = state
+
+    def set_size(self, width, height):
+        self.size = Vector(width, height)
+
+    def set_frame(self):
+        thickness = self.THICKNESS
+        offset = thickness / 2
+        surface = Surface((self.size.x + offset * 2, self.size.y + offset * 2))
+        rect = offset, offset, self.size.x, self.size.y
+        cursor = Sprite(self)
+        if self.flashing:
+            self.hue = (self.hue + 30) % 360
+            surface.set_colorkey((0, 0, 0))
+            color = Color(0, 0, 0)
+            color.hsla = self.hue, 100, 50, 100
+        else:
+            surface.fill((255, 0, 0))
+            surface.set_colorkey((255, 0, 0))
+            color = 0, 0, 0
+        draw.rect(surface, color, rect, thickness)
+        draw.rect(surface, (255, 255, 255), rect, 1)
+        self.clear_frames()
+        self.add_frame(surface)
+
+    def update(self):
+        self.set_frame()
+        Sprite.update(self)
 
 
 class SoundEffects(GameChild):
@@ -497,9 +785,7 @@ class Level(Animation):
         tiles = self.tiles = []
         original = self.original_tiles = []
         for path in sorted(glob(join(directory, "*.png"))):
-            original.append(load(path).convert_alpha())
-            tiles.append(scale(original[-1], [original[-1].get_width() * \
-                                              PictureProcessing.SCALE] * 2))
+            self.add_tile(load(path).convert_alpha())
         colored = self.colored_tiles = {}
         self.set_entire_grid()
         block = 0
@@ -555,6 +841,11 @@ class Level(Animation):
         self.subscribe(self.respond)
         self.set_swap_status()
 
+    def add_tile(self, surface):
+        self.original_tiles.append(surface)
+        self.tiles.append(scale(surface, [surface.get_width() * \
+                                          PictureProcessing.SCALE] * 2))
+
     def set_entire_grid(self, index=None):
         self.grid = []
         ds = self.get_display_surface()
@@ -566,8 +857,8 @@ class Level(Animation):
         return False
 
     def load_default(self):
-        tiles = self.tiles = []
-        original = self.original_tiles = []
+        self.tiles = []
+        self.original_tiles = []
         path = self.get_game().editor.get_default_tile_path()
         if exists(path):
             default = load(path).convert()
@@ -577,9 +868,7 @@ class Level(Animation):
             for y in xrange(default.get_height()):
                 for x in xrange([1, 2, 2, 2, 2, 2, 7, 8][y]):
                     default.set_at((x, y), (200, 200, 200))
-        self.original_tiles = [default]
-        self.tiles = [
-            scale(default, [default.get_width() * PictureProcessing.SCALE] * 2)]
+        self.add_tile(default)
         self.colored_tiles = {}
         self.set_entire_grid(0)
         self.set_swap_status()