Class: SnesUtils::Png2Snes

Inherits:
Object
  • Object
show all
Defined in:
lib/png2snes/png2snes.rb

Constant Summary collapse

CHAR_SIZE =
8

Instance Method Summary collapse

Constructor Details

#initialize(file_path, bpp: 4, alpha: nil, mode7: false, m7_palette_offset: nil) ⇒ Png2Snes

Returns a new instance of Png2Snes.

Raises:

  • (ArgumentError)


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/png2snes/png2snes.rb', line 5

def initialize(file_path, bpp:4, alpha:nil, mode7: false, m7_palette_offset: nil)
  @file_path = file_path
  @file_dir = File.dirname(@file_path)
  @file_name = File.basename(@file_path, File.extname(@file_path))
  @image = ChunkyPNG::Image.from_file(@file_path)

  @mode7 = mode7

  raise ArgumentError, 'Image width and height must be a multiple of sprite size' if (@image.width % CHAR_SIZE != 0) or (@image.height % CHAR_SIZE != 0)

  @pixels = pixels_to_bgr5
  @palette = @pixels.uniq

  if @mode7
    raise ArgumentError, 'mode 7 palette offset must be multiple of 16' if m7_palette_offset % 16 != 0
    raise ArgumentError, 'mode 7 palette offset must be less than 112' if m7_palette_offset > 112
    @m7_palette_offset = m7_palette_offset

    unshift_alpha(alpha) if alpha
    fill_palette
  else
    raise ArgumentError, 'BPP must be 2, 4, or 8' unless [2, 4, 8].include? bpp
    @bpp = bpp

    unshift_alpha(alpha) if alpha
    fill_palette
  end
end

Instance Method Details

#extract_bitplanes(sprite) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/png2snes/png2snes.rb', line 101

def extract_bitplanes sprite
  bitplanes = []
  (0..@bpp-1).each do |plane|
    bitplanes.push sprite.map { |p| p[plane] }
  end

  bitplanes
end

#extract_spritesObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/png2snes/png2snes.rb', line 77

def extract_sprites
  pixel_idx = pixel_indices

  sprite_per_row = @image.width / CHAR_SIZE
  sprite_per_col = @image.height / CHAR_SIZE
  sprite_per_sheet =  sprite_per_row * sprite_per_col

  sprites = []
  (0..sprite_per_sheet-1).each do |s|
    sprite = []
    (0..CHAR_SIZE-1).each do |r|
      offset = (s/sprite_per_row)*sprite_per_row * CHAR_SIZE**2 + s % sprite_per_row * CHAR_SIZE
      if @mode7
        sprite += @pixels[offset + r*sprite_per_row*CHAR_SIZE, CHAR_SIZE]
      else
        sprite += pixel_idx[offset + r*sprite_per_row*CHAR_SIZE, CHAR_SIZE]
      end
    end
    sprites.push(sprite)
  end

  sprites
end

#fill_paletteObject

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
# File 'lib/png2snes/png2snes.rb', line 48

def fill_palette
  target_size = @mode7 ? mode_7_pal_size : 2**@bpp
  missing_colors = target_size - @palette.count
  raise ArgumentError, "Palette size too large for target BPP (#{@palette.count})" if missing_colors < 0

  @palette += [0] * missing_colors
end

#mode_7_pal_sizeObject



56
57
58
# File 'lib/png2snes/png2snes.rb', line 56

def mode_7_pal_size
  128 - @m7_palette_offset
end

#pixel_indicesObject



71
72
73
74
75
# File 'lib/png2snes/png2snes.rb', line 71

def pixel_indices
  pix_idx = @pixels.map { |p| @palette.index(p) }
  pix_idx_bin = pix_idx.map { |i| "%0#{@bpp}b" % i }
  pix_idx_bin.map { |i| i.reverse }
end

#pixels_to_bgr5Object



34
35
36
37
38
39
40
41
42
# File 'lib/png2snes/png2snes.rb', line 34

def pixels_to_bgr5
  @image.pixels.map do |c|
    r = ((c >> 24) & 0xff) >> 3
    g = ((c >> 16) & 0xff) >> 3
    b = ((c >>  8) & 0xff) >> 3

    r | (g << 5) | (b << 10)
  end
end

#unshift_alpha(alpha) ⇒ Object



44
45
46
# File 'lib/png2snes/png2snes.rb', line 44

def unshift_alpha(alpha)
  @palette.unshift(alpha)
end

#write(hex, file_path) ⇒ Object



60
61
62
63
64
# File 'lib/png2snes/png2snes.rb', line 60

def write hex, file_path
  File.open(file_path, 'w+b') do |file|
    file.write([hex.join].pack('H*'))
  end
end

#write_imageObject



110
111
112
# File 'lib/png2snes/png2snes.rb', line 110

def write_image
  @mode7 ? write_image_m7 : write_image_m06
end

#write_image_m06Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/png2snes/png2snes.rb', line 114

def write_image_m06
  sprite_per_row = @image.width / CHAR_SIZE
  sprites = extract_sprites
  sprites_bitplanes = sprites.map { |s| extract_bitplanes s }

  image_bits = ""
  sprites_bitplanes.each do |sprite_bitplanes|
    sprite_bitplane_pairs = sprite_bitplanes.each_slice(2).to_a

    bitplane_bits = ""
    sprite_bitplane_pairs.each do |bitplane|
      (0..CHAR_SIZE-1).each do |r|
        offset = r*CHAR_SIZE
        bitplane_bits += bitplane[0][offset, CHAR_SIZE].join + bitplane[1][offset, CHAR_SIZE].join
      end
    end
    image_bits += bitplane_bits

  end

  image_hex = image_bits.scan(/.{8}/).map { |b| "%02x" % b.to_i(2) }
  write image_hex, File.expand_path("#{@file_name}.tiles", @file_dir)
end

#write_image_m7Object



138
139
140
141
142
143
144
# File 'lib/png2snes/png2snes.rb', line 138

def write_image_m7
  sprites = extract_sprites

  indices = sprites.flatten.map { |color| "%02x" % (@palette.index(color) + @m7_palette_offset) }

  write indices, File.expand_path("#{@file_name}.tiles", @file_dir)
end

#write_paletteObject



66
67
68
69
# File 'lib/png2snes/png2snes.rb', line 66

def write_palette
  palette_hex = @palette.map { |c| ('%04x' % c).scan(/.{2}/).reverse.join }
  write palette_hex, File.expand_path("#{@file_name}.pal", @file_dir)
end