require 'glimmer-dsl-tk' require 'cryptopunks' require 'facets' require 'fileutils' require 'net/http' require 'uri' require 'glimmer/data_binding/observer' require 'yaml' require 'puts_debuggerer' class CryptopunksGui include Glimmer PALETTES = ['Standard'] + (Palette8bit.constants).map(&:name).map {|palette| palette.split('_').map(&:capitalize).join(' ')}.reject { |palette| palette.include?(' ') }.sort STYLES = ['Normal', 'Led', 'Sketch'] COLLECTION_URL_MAP = { 'Punks' => 'https://raw.githubusercontent.com/larvalabs/cryptopunks/master/punks.png', 'Mohawks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/mohawks.png', 'Blondies' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/blondies.png', 'Zombies' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/zombies.png', 'Apes' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/apes.png', 'Aliens' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/aliens.png', 'Golden Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/goldenpunks.png', 'Halloween Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/halloweenpunks.png', 'Front Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/frontpunks.png', 'More Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/morepunks.png', 'Expansion Punks' => 'https://expansionpunks.com/provenance/expansionpunks.png', 'Avalanche Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/avalanchepunks.png', 'Intl Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/intlpunks.png', 'Ape Punks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/apepunks.png', 'Alien Clan' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/alienclan.png', 'Bored Apes Blue' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_blue.png', 'Bored Apes Red' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_red.png', 'Bored Apes Neon Glow' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_neon_glow.png', 'Bored Apes Stars and Stripes' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_stars_and_stripes.png', 'Bored Apes Acid' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/boredapes_acid.png', 'Cool Cats' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats.png', 'Cool Cats Mohawks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_mohawks.png', 'Cool Cats Ninjas' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_ninjas.png', 'Cool Cats TV Heads' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_tvheads.png', 'Cool Cats Pirates' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_pirates.png', 'Cool Cats Unicorns' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_unicorns.png', 'Cool Cats Dragons' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_dragons.png', 'Cool Cats Frogs' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/coolcats_frogs.png', 'Pudgy Penguins' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/pudgypenguins.png', 'Dodge' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/dodge.png', 'Rocks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/rocks.png', 'Punk Rocks' => 'https://raw.githubusercontent.com/cryptopunksnotdead/programming-cryptopunks/master/i/punkrocks.png', 'Tulips' => 'https://raw.githubusercontent.com/cryptopunksnotdead/awesome-24px/master/collection/tulips.png', } attr_accessor :collection, :image_index, :zoom, :palette, :style, :led_spacing, :led_round_corner, :sketch_line, :flip, :mirror def initialize initialize_punk_directory initialize_collection load_config initialize_defaults observe_image_attribute_changes create_gui self.image_index = 0 @root.open end def collection_options COLLECTION_URL_MAP.keys end def palette_options PALETTES end def style_options STYLES end def initialize_punk_directory @punk_directory = @punk_config_directory = File.join(Dir.home, 'cryptopunks') FileUtils.mkdir_p(@punk_directory) end def initialize_collection return if @collection && @collection == @last_collection @collection ||= COLLECTION_URL_MAP.keys.first url = COLLECTION_URL_MAP[@collection] @punk_file = File.join(@punk_config_directory, File.basename(url, '.png')) File.write(@punk_file, Net::HTTP.get(URI(url))) unless File.exist?(@punk_file) @images ||= {} @images[@collection] ||= Punks::Image::Composite.read(@punk_file) @last_collection = @collection self.image_index = 0 @image_index_spinbox.to = @images[@collection].size - 1 if @image_index_spinbox end def load_config @punk_config_file = File.join(@punk_config_directory, 'cryptopunks.yml') FileUtils.touch(@punk_config_file) @punk_config = YAML.load(File.read(@punk_config_file)) || {punk_directory: @punk_directory} @punk_directory = @punk_config[:punk_directory] end def save_config File.write(@punk_config_file, YAML.dump(@punk_config)) end def initialize_defaults @collection = COLLECTION_URL_MAP.keys.first @zoom = 12 @palette = PALETTES.first @style = STYLES.first @led_spacing = 2 @led_round_corner = false @sketch_line = 1 @mirror = false @flip = false end def observe_image_attribute_changes observer = Glimmer::DataBinding::Observer.proc { generate_image } observer.observe(self, :collection) observer.observe(self, :image_index) observer.observe(self, :zoom) observer.observe(self, :palette) observer.observe(self, :style) observer.observe(self, :led_spacing) observer.observe(self, :led_round_corner) observer.observe(self, :sketch_line) observer.observe(self, :mirror) observer.observe(self, :flip) end def generate_image initialize_collection return if @image_index.to_i > @images[@collection].size image_location = File.join(@punk_directory, "#{@collection.gsub(' ', '').downcase}-#{@image_index}#{"x#{@zoom}" if @zoom.to_i > 1}#{"-#{@palette.underscore}" if @palette != PALETTES.first}#{"-#{@style.underscore}" if @style != STYLES.first}.png") puts "Writing punk image to #{image_location}" selected_punk = @images[@collection][@image_index.to_i] selected_punk = selected_punk.change_palette8bit(Palette8bit.const_get(@palette.gsub(' ', '_').upcase.to_sym)) if @palette != PALETTES.first @original_zoom = @zoom if @style != STYLES.first style_options = {} if @style == 'Led' style_options[:spacing] = @led_spacing.to_i style_options[:round_corner] = @led_round_corner end if @style == 'Sketch' style_options[:line] = @sketch_line.to_i end selected_punk = selected_punk.send(@style.underscore, @zoom.to_i, **style_options) end selected_punk = selected_punk.mirror if @mirror selected_punk = selected_punk.flip if @flip selected_punk = selected_punk.zoom(@zoom.to_i) if @style == STYLES.first selected_punk.save(image_location) @image_label.image = image_location @output_location_entry.text = image_location @previous_style = @style notify_observers(:zoom) if @zoom != @original_zoom end def create_gui @root = root { title 'CryptoPunks GUI' iconphoto File.expand_path('../icons/cryptopunks-gui.png', __dir__) frame { label { text 'Collection:' } combobox { readonly true text <=> [self, :collection] } label { text 'Image Index:' } @image_index_spinbox = spinbox { from 0 to @images[@collection].size - 1 text <=> [self, :image_index] } label { text 'Zoom:' } spinbox { from 1 to 72 text <=> [self, :zoom] } label { text 'Palette:' } combobox { readonly true text <=> [self, :palette] } label { text 'Style:' } combobox { readonly true text <=> [self, :style, after_write: ->(val) {add_style_options}] } @style_options_frame = frame { padding 0 } frame { padding 0 checkbutton { grid row: 0, column: 0, column_weight: 0 variable <=> [self, :mirror] } label { grid row: 0, column: 1 text 'Mirror' on('Button-1') do self.mirror = !mirror end } checkbutton { grid row: 0, column: 2 variable <=> [self, :flip] } label { grid row: 0, column: 3 text 'Flip' on('Button-1') do self.flip = !flip end } } label { text 'Output Location:' } frame { padding 0 @output_location_entry = entry { grid row: 0, column: 0 readonly true width 47 } button { grid row: 0, column: 1 text '...' width 1.1 on('command') do @punk_directory = choose_directory(parent: @root) @punk_config[:punk_directory] = @punk_directory save_config generate_image end } } @image_label = label { grid row_weight: 1 } } } end def add_style_options @style_options_frame.content { @style_options_frame.children.each(&:destroy) if @style == 'Led' frame { padding 0 label { grid row: 0, column: 0, column_weight: 0 text 'Spacing:' } spinbox { grid row: 0, column: 1 from 1 to 72 text <=> [self, :led_spacing] } checkbutton { grid row: 0, column: 2 variable <=> [self, :led_round_corner] } label { grid row: 0, column: 3 text 'Round Corner' on('Button-1') do self.led_round_corner = !led_round_corner end } } elsif @style == 'Sketch' frame { padding 0 label { grid row: 0, column: 0, column_weight: 0 text 'Line:' } spinbox { grid row: 0, column: 1 from 1 to 72 text <=> [self, :sketch_line] } } else frame { # filler padding 0 height 0 width 0 } end } end end CryptopunksGui.new.launch