class Snowy::Canvas

256 indexed canvas

Constants

MAX_PIXEL
MIN_PIXEL

Attributes

color[R]
height[R]
matrix[R]
palette[R]
pixels[R]
width[R]

Public Class Methods

new(width, height, background = 0, color = 1, palette = [WHITE, BLACK]) click to toggle source
# File lib/snowy.rb, line 71
def initialize(width, height, background = 0, color = 1, palette = [WHITE, BLACK])
  @width = width.to_i
  @height = height.to_i
  if @width < MIN_PIXEL || @width > MAX_PIXEL ||
     @height < MIN_PIXEL || @height > MAX_PIXEL
    raise ArgumentError,
          "width and height are too small or large (given %d x %d, expect %d..%d)" %
            [@width, @height, MIN_PIXEL, MAX_PIXEL]
  end

  @pixels = [background].pack("C") * (@width * @height)
  @matrix = Matrix.new
  @palette = palette
  @color = color
end

Public Instance Methods

[](x, y)
Alias for: getpixel
[]=(x, y, level)
Alias for: setpixel
dot_by_char(x, y, seq, colormap = { "*" => 2 }) click to toggle source

plot dot by char

example:

  # plot dot "Error(TIMEDOUT)"
  canv.dot_by_char(5, 5, <<-CODE, { "*" => 3 })
***                  * *** *** *   * *** **  *** * * *** *
*                   *   *   *  ** ** *   * * * * * *  *   *
*** *** *** *** *** *   *   *  * * * *** * * * * * *  *   *
*   *   *   * * *   *   *   *  * * * *   * * * * * *  *   *
*** *   *   *** *    *  *  *** *   * *** **  *** ***  *  *
  CODE
# File lib/snowy.rb, line 191
def dot_by_char(x, y, seq, colormap = { "*" => 2 })
  x0 = x
  seq.each_char do |ch|
    case ch
    when "\n"
      x = x0
      y += 1
    when " "
      x += 1
    else
      if color = colormap[ch]
        setpixel(x, y, color)
      end
      x += 1
    end
  end

  self
end
export_to_png(io = "".b, level: Zlib::DEFAULT_COMPRESSION) click to toggle source
# File lib/snowy.rb, line 342
    def export_to_png(io = "".b, level: Zlib::DEFAULT_COMPRESSION)
      io << [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
      storechunk = ->(code, chunk) {
        crc = Zlib.crc32(chunk, Zlib.crc32(code))
        io << [chunk.bytesize].pack("N")
        io << code << chunk
        io << [crc].pack("N")
      }

      ## IDAT
      ##  color
      ##  type  bit depth
      ##  0     1,2,4,8,16  immediate grayscale sample
      ##  2     8,16        immediate RGB sample
      ##  3     1,2,4,8     palette sample (need PLTE chunk)
      ##  4     8,16        immediate grayscale sample with alpha
      ##  6     8,16        immediate RGB sample with alpha
      colorbits = 8
      colortype = 3
      storechunk["IHDR", [width, height, colorbits, colortype, 0, 0, 0].pack("NNCCCCC")]

      ## PLTE
      plte = palette.reduce("".b) { |a, e|
        a << e.pack_rgb
      }
      storechunk["PLTE", plte]

      ## tRNS
      ##    Alpha for palette index 0:  1 byte
      ##    Alpha for palette index 1:  1 byte
      ##    ...etc...
      trns = palette.reduce([]) { |a, e| a << e.get_alpha }
      storechunk["tRNS", trns.pack("C*")]

      ## iTXt
      ##    Keyword:             1-79 bytes (character string)
      ##    Null separator:      1 byte
      ##    Compression flag:    1 byte
      ##    Compression method:  1 byte
      ##    Language tag:        0 or more bytes (character string)
      ##    Null separator:      1 byte
      ##    Translated keyword:  0 or more bytes
      ##    Null separator:      1 byte
      ##    Text:                0 or more bytes
      storechunk["iTXt", ["snowy", 0, 0, <<-'SNOWY'].pack("a*xCCxxa*")]
This image is generated by snowy <https://rubygems.org/gems/snowy>
      SNOWY
      storechunk["iTXt", ["LICENSING", 0, 0, <<-'LICENSING'].pack("a*xCCxxa*")]
Creative Commons License Zero (CC0 / Public Domain)
See <https://creativecommons.org/publicdomain/zero/1.0/>
      LICENSING

      ## IDAT
      scanline = ->(lines, line) {
        lines << "\0" << line
      }
      scanline ||= ->(lines, line) {
        v = 0
        line.each_byte.with_index { |vv, i|
          line.setbyte(i, vv - v)
          v = vv
        }
        lines << "\1" << line
      }
      lines = height.times.reduce("".b) { |a, h|
        line = pixels.byteslice(h * width, width)
        scanline[a, line]
      }
      storechunk["IDAT", Zlib.deflate(lines, level)]

      storechunk["IEND", ""]

      io
    end
fill(color = @color) click to toggle source
# File lib/snowy.rb, line 114
def fill(color = @color)
  @pixels.bytesize.times do |i|
    @pixels.setbyte(i, color)
  end
  self
end
getpixel(x, y) click to toggle source
# File lib/snowy.rb, line 90
def getpixel(x, y)
  x = x.to_i
  y = y.to_i
  if validate_point(x, y)
    getpixel!(x, y)
  else
    nil
  end
end
Also aliased as: []
getpixel!(x, y) click to toggle source
# File lib/snowy.rb, line 139
def getpixel!(x, y)
  @pixels.getbyte(x + y * @width)
end
halfdown!() click to toggle source
# File lib/snowy.rb, line 312
def halfdown!
  pixels = @pixels
  height = @height
  width2 = width >> 1
  height2 = height >> 1
  height2.times do |y|
    width2.times do |x|
      xx = x * 2
      yy = y * 2
      xx1 = xx + 1
      yy1 = (yy + 1) * height
      yy = yy * height
      pixels.setbyte(x + y * width2,
                     (pixels.getbyte(xx + yy) +
                      pixels.getbyte(xx1 + yy) +
                      pixels.getbyte(xx + yy1) +
                      pixels.getbyte(xx1 + yy1)) / 4)
    end
  end

  @width = width2
  @height = height2
  @pixels[(width2 * height2) .. -1] = ""

  self
end
init_matrix() click to toggle source
# File lib/snowy.rb, line 214
def init_matrix
  matrix.init
  self
end
mult_matrix(matrix1) click to toggle source
# File lib/snowy.rb, line 235
def mult_matrix(matrix1)
  matrix.mult(matrix1)
  self
end
plotscanline(y, x0, x1) click to toggle source
# File lib/snowy.rb, line 158
def plotscanline(y, x0, x1)
  y = y.round
  x0 = x0.round
  x1 = x1.round

  if y >= 0 && y < height
    (x0, x1) = x1, x0 if x0 > x1
    if x0 < width && x1 >= 0
      x0 = [0, x0].max
      x1 = [x1, width].min
      plotscanline! y, x0, x1
    end
  end

  self
end
plotscanline!(y, x0, x1) click to toggle source
# File lib/snowy.rb, line 147
def plotscanline!(y, x0, x1)
  w = width
  px = pixels
  i = color
  x0.upto(x1 - 1) { |x| px.setbyte(x + y * w, i) }
  self
end
pop_matrix(saved_matrix) click to toggle source
# File lib/snowy.rb, line 230
def pop_matrix(saved_matrix)
  matrix.load(saved_matrix)
  self
end
push_matrix() { || ... } click to toggle source
# File lib/snowy.rb, line 219
def push_matrix
  save = matrix.dup
  return save unless block_given?

  begin
    yield
  ensure
    matrix.load(save)
  end
end
rotate(rad) click to toggle source
# File lib/snowy.rb, line 254
def rotate(rad)
  matrix.rotate(rad)
  self
end
rotate_deg(deg) click to toggle source
# File lib/snowy.rb, line 259
def rotate_deg(deg)
  matrix.rotate(Math::PI * deg / 180)
  self
end
scale(ax, ay, aw = 1) click to toggle source
# File lib/snowy.rb, line 249
def scale(ax, ay, aw = 1)
  matrix.scale(ax, ay, aw)
  self
end
setpixel(x, y, level) click to toggle source
# File lib/snowy.rb, line 102
def setpixel(x, y, level)
  x = x.to_i
  y = y.to_i
  validate_point(x, y) and setpixel!(x, y, level)
  self
end
Also aliased as: []=
setpixel!(x, y, color) click to toggle source
# File lib/snowy.rb, line 143
def setpixel!(x, y, color)
  @pixels.setbyte(x + y * @width, color)
end
test_point(x, y) click to toggle source
# File lib/snowy.rb, line 129
def test_point(x, y)
  unless validate_point(x, y)
    raise ArgumentError,
          "x and y are out of canvas (given (%d, %d), expect (0, 0) .. (%d, %d))" %
            [x, y, @width - 1, @height - 1]
  end

  nil
end
transform(x, y, w = 1) click to toggle source
# File lib/snowy.rb, line 240
def transform(x, y, w = 1)
  matrix.transform(x, y, w)
end
translate(dx, dy, dw = 1) click to toggle source
# File lib/snowy.rb, line 244
def translate(dx, dy, dw = 1)
  matrix.translate(dx, dy, dw)
  self
end
triangle(x0, y0, x1, y1, x2, y2) click to toggle source
# File lib/snowy.rb, line 267
def triangle(x0, y0, x1, y1, x2, y2)
  (x0, y0) = transform(x0, y0)
  (x1, y1) = transform(x1, y1)
  (x2, y2) = transform(x2, y2)

  x0 = x0.round
  x1 = x1.round
  x2 = x2.round
  y0 = y0.round
  y1 = y1.round
  y2 = y2.round

  (x0, y0, x1, y1) = x1, y1, x0, y0 if y0 > y1
  (x1, y1, x2, y2) = x2, y2, x1, y1 if y1 > y2
  (x0, y0, x1, y1) = x1, y1, x0, y0 if y0 > y1

  if y0 == y2
    (a, *, b) = [x0, x1, x2].sort
    plotscanline(y0, a, b)
  else
    d1 = (x1 - x0) / (y1 - y0).to_f
    d2 = (x2 - x0) / (y2 - y0).to_f
    (y0 ... y1).step(1) do |py|
      dy = py - y0
      plotscanline(py, x0 + dy * d1, x0 + dy * d2)
    end

    if y1 == y2
      plotscanline(y1, x1, x2)
    else
      d0 = (x0 - x2) / (y0 - y2).to_f
      d1 = (x1 - x2) / (y1 - y2).to_f
      (y1 .. y2).step(1) do |py|
        dy = (py - y2)
        plotscanline(py, x2 + dy * d1, x2 + dy * d0)
      end
    end
  end

  self
end
validate_point(x, y) click to toggle source
# File lib/snowy.rb, line 121
def validate_point(x, y)
  if x < 0 || x >= @width || y < 0 || y >= @height
    false
  else
    true
  end
end